从Java BigDecimal转换为double的精度下降


问题内容

我正在使用完全基于双精度的应用程序,并且在将字符串解析为双精度的一种实用程序方法中遇到麻烦。我找到了一个解决方法,其中使用BigDecimal进行转换可以解决该问题,但是当我打算将BigDecimal转换回double时又提出了另一个问题:我失去了几个精度位。例如:

import java.math.BigDecimal;
import java.text.DecimalFormat;

public class test {
    public static void main(String [] args){
        String num = "299792.457999999984";
        BigDecimal val = new BigDecimal(num);
        System.out.println("big decimal: " + val.toString());
        DecimalFormat nf = new DecimalFormat("#.0000000000");
        System.out.println("double: "+val.doubleValue());
        System.out.println("double formatted: "+nf.format(val.doubleValue()));
    }
}

这将产生以下输出:

$ java test
big decimal: 299792.457999999984
double: 299792.458
double formatted: 299792.4580000000

格式化的double值表明它已经失去了第三位的精度(应用程序要求较低的精度)。

如何获得BigDecimal来保留这些额外的精度?

谢谢!


赶上这篇文章后更新。有人提到这超出了double数据类型的精度。除非我未正确阅读此参考文献:http
:
//java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.2.3,
否则double原语的最大指数值为E max = 2 K-1 -1,并且标准实现为K = 11。因此,最大指数应为511,不是吗?


问题答案:

double使用该数字,您已达到的最高精度。不能做 在这种情况下,该值将四舍五入。从的转换BigDecimal是无关的,并且精度问题是相同的。例如:

System.out.println(Double.parseDouble("299792.4579999984"));
System.out.println(Double.parseDouble("299792.45799999984"));
System.out.println(Double.parseDouble("299792.457999999984"));

输出为:

299792.4579999984
299792.45799999987
299792.458

对于这些情况double,小数点后的精度超过3位。对于您的数字,它们恰好是零,这是您可以放入的最接近的表示形式double。在这种情况下,它更接近四舍五入,因此您的9似乎消失了。如果您尝试这样做:

System.out.println(Double.parseDouble("299792.457999999924"));

您会注意到它保留了9,因为它接近四舍五入:

299792.4579999999

如果您要求保留数字中的 所有
数字,则必须更改在上操作的代码double。您可以BigDecimal代替它们使用。如果您需要性能,那么尽管我不知道有任何可用的库,但您可能想将BCD作为一种选择。


为了响应您的更新:双精度浮点数的最大指数实际上是1023。但这不是您的限制因素。您的数字超出了代表有效位数的52个小数位的精度,请参阅IEEE
754-1985

使用此浮点转换可以看到您的二进制数字。由于262144(2
^ 18)最接近,因此指数为18。如果您使用小数位然后以二进制形式向上或向下一位,您会发现没有足够的精度来表示您的数字:

299792.457999999900 // 0010010011000100000111010100111111011111001110110101
299792.457999999984 // here's your number that doesn't fit into a double
299792.458000000000 // 0010010011000100000111010100111111011111001110110110
299792.458000000040 // 0010010011000100000111010100111111011111001110110111