从java BigDecimal转换为double的精度丢失

我正在使用一个完全基于双精度的应用程序,并且在将字符串解析为双精度的实用程序方法中遇到了麻烦。我找到了一个修复程序,其中使用BigDecimal进行转换可以解决问题,但是当我将BigDecimal转换回双精度时,会引发另一个问题:我丢失了几个精度位置。例如:

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

格式化的双精度表示它在第三位之后丢失了精度(应用程序需要那些较低的精度位置)。

如何获取 BigDecimal 来保留这些额外的精度位置?

谢谢!


赶上这篇文章后更新。有几个人提到这超出了双精度数据类型的精度。除非我错误地阅读了这个参考:http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.2.3 那么双基元的最大指数值为Emax = 2K-1-1,标准实现具有K = 11。那么,最大指数应该是511,不是吗?


答案 1

您已达到具有该数字的 a 的最大精度。这是做不到的。在这种情况下,该值将向上舍入。从 的转换是不相关的,精度问题也是相同的。例如,请参阅以下内容:doubleBigDecimal

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

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

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

您会注意到它保留了您的9,因为它更接近于向下舍入:

299792.4579999999

如果需要保留数字中的所有数字,则必须更改在 上运行的代码。你可以用代替它们。如果您需要性能,那么您可能希望将BCD作为一个选项进行探索,尽管我不知道有任何库。doubleBigDecimal


响应您的更新:双精度浮点数的最大指数实际上是 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

答案 2

问题是 a 可以保存 15 个数字,而 a 可以保存任意数字。当您调用 时,它会尝试应用舍入模式以删除多余的数字。但是,由于输出中有很多 9,这意味着它们不断被舍入到 0,并带有下一个最高数字的区位。doubleBigDecimaltoDouble()

为了尽可能保持精度,您需要更改 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());