数字格式解析问题

3
我对将字符串解析为双精度浮点数时遇到的奇怪“错误”感到困惑。
我已经设置了NumberFormat属性和符号。
当传递一个带有15个数字和2个小数位的字符串(例如str =“333333333333333,33”)并使用Number num = NumberFormat.parse(str)进行解析时,结果会省略一位数字。
num的实际值为3.333333333333333E14
不过,似乎对于所有1、2和4的字符串都能正常工作…
谁能解释一下?
问候 Enrico
2个回答

6
简短回答:由于舍入误差。
(double) 111111111111111.11 != (double) 111111111111111.1

但是
(double) 333333333333333.33 == (double) 333333333333333.3

如果您想要更高的精度,请使用setParseBigDecimal,解析将返回一个BigDecimal。
为什么会这样?因为您已经到达了双精度的精度极限。17个1是可以被表示的。两个2也是如此,因为double存储二的幂次方,所有17个1的每个二的幂次方,例如17个4和17个8都可以被表示。
然而,17个3需要比double多一个位来表示这个值,这最后一位被截断。同样,17个5、6和9也存在舍入误差。
double[] ds = {
        111111111111111.11,
        222222222222222.22,
        333333333333333.33,
        444444444444444.44,
        555555555555555.55,
        666666666666666.66,
        777777777777777.77,
        888888888888888.88,
        999999999999999.99};
for (double d : ds) {
    System.out.println(d + " - " + new BigDecimal(d));
}

打印以下内容。在打印前,double 会被略微舍入,而 BigDecimal 则会显示 double 表示的精确值。
1.1111111111111111E14 - 111111111111111.109375
2.2222222222222222E14 - 222222222222222.21875
3.333333333333333E14 - 333333333333333.3125
4.4444444444444444E14 - 444444444444444.4375
5.5555555555555556E14 - 555555555555555.5625
6.666666666666666E14 - 666666666666666.625
7.777777777777778E14 - 777777777777777.75
8.888888888888889E14 - 888888888888888.875
1.0E15 - 1000000000000000

@aioobe,从你这里说出来,意义非凡。 ;) - Peter Lawrey
谢谢 :-) 我对你的第二个表达式感到非常惊讶,所以我写了一个后续问题 - aioobe
结果发现答案很明显 :-/ 有点尴尬。 - aioobe

5
DecimalFormat.parse方法在此情况下返回Double类型的数值,该类型具有有限的精度

您不能指望它总是能够准确地返回表示输入的数字。

您可以使用BigDecimal.setParseBigDecimal方法允许数字格式从解析方法中返回一个BigDecimal类型的数值。这个Number类型能够以任意精度表示您的数值。(感谢 @Peter Lawrey 指出!)


有没有绕过的方法,使其不解析为 double,而是保留为 long? - Mallinok
long 是一种整数类型,因此您将失去小数部分(,33)。 - aioobe
@aioobe,你是否正在查看Java 1.4的文档?该方法是在Java 5.0中添加的。http://download.oracle.com/javase/6/docs/api/java/text/DecimalFormat.html#setParseBigDecimal%28boolean%29 - Peter Lawrey
不客气。请务必点击复选框,将@Peter Lawreys的优秀答案标记为已接受。 - aioobe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接