[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.DecimalFormat
是NumberFormat
的一个具体子类,用于格式化十进制数。它允许你为数字、整数和小数指定模式。
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 参考文献
本文作者:
千千寰宇
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!