[Java] 浮點數的精度丟失問題與精度控制方法

千千寰宇發表於2024-11-19

1 需求描述

  • 場景1:兩個整型相除,如何保證運算結果為浮點數?如何控制運算結果的精度(小數位數)?
  • 場景2:針對一個浮點數,如何控制其精度(小數位數)?

2 試驗

場景:兩整型數相除,控制運算結果、浮點數的精度

Long number1 = 110600L;
int number2 = 999;
int scale = 2;//精度n(保留n位小數)

// 【錯誤示範】
//方式1
double i = number1/number2; // 110.0 (結果錯誤,精度丟失)
//方式2
double i = (double) (number1/number2) // 110.0 (結果錯誤,精度丟失)

//方式3
BigDecimal bd1 = BigDecimal.valueOf(number1);
BigDecimal bd2 = BigDecimal.valueOf(number2);
//(可能)將報錯: java.lang.AthmeticException : Non-terminating decimal expansion; no exact representable decimal result.()
// java.lang.AthmeticException : 非終止小數展開;沒有可精確表示的十進位制結果 (意譯: 這個除法除不盡,會得到一個無限小數,導致異常。)
BigDecimal bd3 = bd1.divide(bd2).setScale( scale, RoundingMode.HALF_UP);


//【正確示範】不丟失精度
//方式4 轉換其中至少1個整型為浮點型 | 利用java運算時,運算結果型別預設等於精度最高的型別
double number3 = (double) number1/number2; //等效於 : ((double) number1)/number2 // 110.71071071071071 (結果正確,精度未丟失)




//方式5 BigDecimal + BigDecimal.divide(被除數, 精度n:=保留n位小數, 向上入|向下舍)
BigDecimal bd1 = BigDecimal.valueOf(number1);
BigDecimal bd2 = BigDecimal.valueOf(number2);
BigDecimal bd3 = bd1.divide(bd2 , scale, RoundingMode.HALF_UP);//解決方式3的異常情況
bd3.doubleValue();//輸出為浮點數 : 110.71d
bd3.toString();//輸出為字串 : "110.71"

DecimalFormat df = new DecimalFormat("#0.0000");
String dfResult = df.format(bd3);//格式化浮點數 : "110.7100"
String dfResult = df.format(110.32535235);// "110.3254" (預設會四捨五入)

3 總結

場景1:2個整數相除,計算結果預設為整型,導致精度丟失問題

方法1:至少將其中一個整型轉為浮點數,則:運算結果一定是浮點數

方法2:利用 BigDecimal + BigDecimal.divide(被除數, 精度n:=保留n位小數, 向上入|向下舍)

場景2:如何對浮點數進行四捨五入運算?

方法1: Math.round(number) + Math.pow(10, scale) + 乘法、除法運算

Math.round() 方法可以將浮點數四捨五入到最接近的整數。
如果你需要四捨五入到特定的小數位數:

  • 首先,可以先將數字乘以10的n次方(n為你想要保留的小數位數)
  • 然後,使用Math.round()進行四捨五入,最後再除以10的n次方得到結果。
double number3 = (double) number1/number2;

long x = Math.round( number3 * Math.pow(10, scale)  );
double y = Math.pow(10, scale); //運算結果為浮點型
double result = x/y;//110.71

方法2: BigDecimal(number).setScale(scale, BigDecimal.ROUND_HALF_UP)

double number3 = (double) number1/number2;

BigDecimal result = new BigDecimal(number3).setScale(scale, BigDecimal.ROUND_HALF_UP);
result.doubleValue(); // 110.71
result.toString();//"110.71"

如果目標浮點數是除法運算後的結果,還可在運算過程中利用BigDecimal.divide(被除數, 精度n:=保留n位小數, 向上入|向下舍)方法,直接在運算時控制運算結果的精度:

//核心思路: BigDecimal + BigDecimal.divide(被除數, 精度n:=保留n位小數, 向上入|向下舍)

BigDecimal bd1 = BigDecimal.valueOf(number1);
BigDecimal bd2 = BigDecimal.valueOf(number2);
BigDecimal bd3 = bd1.divide(bd2 , scale, RoundingMode.HALF_UP);
bd3.doubleValue();//輸出為浮點數 : 110.71d
bd3.toString();//輸出為字串 : "110.71"

DecimalFormat df = new DecimalFormat("#0.0000");
String dfResult = df.format(bd3);//格式化浮點數 : "110.7100"
String dfResult = df.format(110.32535235);// "110.3254" (預設會四捨五入)

方法3:String.format("%.2f")

雖然String.format()方法主要用於格式化字串,但它也可以用於四捨五入浮點數到指定的小數位數。
該方法不直接改變數字,而是將其格式化為包含指定小數位數的字串。

double number3 = (double) number1/number2;//110.71071071071071

String s = String.format("%.2f", number3); // "110.71"

double d = Double.parseDouble(s); // 110.71d

注意,使用String.format()方法時,結果是一個字串,如果你需要將其作為浮點數進行進一步操作,可以使用Double.parseDouble()將其轉換回double型別。

方法4:使用DecimalFormat

  • java.text.DecimalFormatNumberFormat的一個具體子類,用於格式化十進位制數。它允許你為數字、整數和小數指定模式。
double number3 = (double) number1/number2;//110.71071071071071

DecimalFormat decimalFormat = new DecimalFormat("#.##");//保留2位小數
String s = decimalFormat.format(number3);//"110.71"

Double d = Double.parseDouble(s); // 110.71d

String.format()類似,DecimalFormat的結果也是一個字串,可以透過Double.parseDouble()轉換回double型別。

X 參考文獻

  • Non-terminating decimal expansion; no exact representable decimal result. - CSDN
  • Java中常見的幾種四捨五入方法總結 - jb51.net

相關文章