[Java] 浮点数的精度丢失问题与精度控制方法

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 参考文献

posted @ 2024-11-19 11:12  千千寰宇  阅读(77)  评论(0编辑  收藏  举报