如何避免使用Java的Double.parseDouble方法时丢失精度
你遇到的精度丢失是二进制浮点数的本质特性导致的,并非Double.parseDouble方法的bug。先看你给出的示例:
Double.parseDouble("-74.858546917546052") returns -> -74.85854691754605 Double.parseDouble("81.782256145962017") returns -> 81.78225614596202 Double.parseDouble("54.623193637334667") returns -> 54.623193637334666 Double.parseDouble("90.693502119687192") returns -> 90.6935021196872 Double.parseDouble("-56.280643193737337") returns -> -56.28064319373734
问题原因
Java的double是64位IEEE 754双精度浮点数,存储结构分为三部分:
- 1位符号位
- 11位指数位
- 52位尾数位(加上1位隐藏的最高位,总共53位有效精度)
53位二进制有效精度转换成十进制后,大约对应15-17位有效数字——这里的核心是「有效数字」而非「小数位数」。比如你的输入-74.858546917546052,整数部分2位+小数部分15位,总共17位有效数字,已经触及double的精度上限。
更关键的是:大部分十进制小数无法被二进制浮点数精确表示。就像十进制无法精确表示1/3(0.333...)一样,很多十进制小数在二进制中是无限循环的,double只能存储其近似值,最终表现为舍入后的结果。
当输入小数位数在13位及以下时,加上整数部分的有效数字总数通常不超过15位,double可以精确表示;但小数位数到14、15位时,总有效数字超过15位,就必须舍入到最近的可表示double值。
解决方案
1. 仅需贴近原数值,无需精确计算
Double.parseDouble的行为是符合标准的——它已经返回了与输入字符串最接近的double值(遵循IEEE 754的「向最近值舍入,偶数优先」规则)。这种情况下无需额外处理,接受舍入结果即可。
2. 需要精确存储和计算14/15位小数
直接放弃double,改用java.math.BigDecimal。BigDecimal可以精确表示任意精度的十进制数,完全避免浮点数舍入误差:
// 用字符串构造BigDecimal,保证精度不丢失 BigDecimal preciseValue = new BigDecimal("-74.858546917546052"); // 精确计算示例 BigDecimal result = preciseValue.add(new BigDecimal("0.000000000000001")); // 输出原精度数值 System.out.println(preciseValue.toString()); // 输出-74.858546917546052
注意:不要使用BigDecimal(double val)构造方法,因为double本身已是近似值,必须用字符串构造才能保证精度。
3. 必须用double类型,但需控制输出精度
如果业务要求必须使用double,可以先将字符串解析为BigDecimal再转成double,然后用DecimalFormat控制输出格式,尽量还原原数值:
String inputStr = "-74.858546917546052"; BigDecimal bd = new BigDecimal(inputStr); double d = bd.doubleValue(); // 格式化输出15位小数 DecimalFormat df = new DecimalFormat("#.#################"); System.out.println(df.format(d));
这种方式只能尽量贴近原字符串,无法100%保证一致,因为double已经丢失了部分精度。
内容的提问来源于stack exchange,提问作者CoderGuy

