丢失精度从java BigDecimal转换为double

Edw*_*ges 12 java floating-point precision double 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()));
    }
}
Run Code Online (Sandbox Code Playgroud)

这会产生以下输出:

$ java test
big decimal: 299792.457999999984
double: 299792.458
double formatted: 299792.4580000000
Run Code Online (Sandbox Code Playgroud)

格式化的double表明它在第三位之后失去了精度(应用程序需要较低的精度位置).

如何让BigDecimal保留那些额外的精度位置?

谢谢!


追上这篇文章后更新.有人提到这超出了双数据类型的精度.除非我错误地阅读此引用: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,不是吗?

Whi*_*g34 21

您已达到该数字的最大精度double.它无法完成.在这种情况下,该值会向上舍入.转换BigDecimal是无关的,精度问题无论如何都是相同的.看到这个例子:

System.out.println(Double.parseDouble("299792.4579999984"));
System.out.println(Double.parseDouble("299792.45799999984"));
System.out.println(Double.parseDouble("299792.457999999984"));
Run Code Online (Sandbox Code Playgroud)

输出是:

299792.4579999984
299792.45799999987
299792.458
Run Code Online (Sandbox Code Playgroud)

对于这些情况double,小数点后的精度超过3位.它们恰好是你的数字的零,这是你可以适应的最接近的代表double.在这种情况下它更接近它,​​所以你的9似乎消失了.如果你试试这个:

System.out.println(Double.parseDouble("299792.457999999924"));
Run Code Online (Sandbox Code Playgroud)

你会发现它保留了你的9,因为它更接近于向下舍入:

299792.4579999999
Run Code Online (Sandbox Code Playgroud)

如果您要求保留号码中的所有数字,则必须更改运行的代码double.你可以用它BigDecimal来代替它们.如果你需要性能,那么你可能想要探索BCD作为一个选项,虽然我不知道任何库.


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

使用此浮点转换以二进制形式查看您的数字.指数为18,因为262144(2 ^ 18)最近.如果取小数位并以二进制形式向上或向下移动,您可以看到没有足够的精度来表示您的数字:

299792.457999999900 // 0010010011000100000111010100111111011111001110110101
299792.457999999984 // here's your number that doesn't fit into a double
299792.458000000000 // 0010010011000100000111010100111111011111001110110110
299792.458000000040 // 0010010011000100000111010100111111011111001110110111
Run Code Online (Sandbox Code Playgroud)

  • @Anon:你在说什么?使用`double`没有解决OP的问题. (4认同)

Ano*_*non 5

问题是 adouble可以容纳 15 位数字,而 aBigDecimal可以容纳任意数字。当您调用 时toDouble(),它会尝试应用舍入模式来删除多余的数字。但是,由于输出中有很多 9,这意味着它们会不断向上取整为 0,并进位到下一个最高位。

为了尽可能保持精度,您需要更改 BigDecimal 的舍入模式,以便截断:

BigDecimal bd1 = new BigDecimal("12345.1234599999998");
System.out.println(bd1.doubleValue());

BigDecimal bd2 = new BigDecimal("12345.1234599999998", new MathContext(15, RoundingMode.FLOOR));
System.out.println(bd2.doubleValue());
Run Code Online (Sandbox Code Playgroud)