丢失精度从java BigDecimal转换为double

时间:2011-04-21 20:45:08

标签: java floating-point double precision 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

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

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

谢谢!


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

5 个答案:

答案 0 :(得分:21)

您已达到具有该数字的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

使用this floating-point conversion查看二进制数字。指数为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

答案 1 :(得分:3)

问题是double可以容纳15位数,而BigDecimal可以容纳任意数字。当您呼叫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());

答案 2 :(得分:2)

只打印那么多个数字,这样,当将字符串解析为double时,它将产生完全相同的值。

可以在Double#toString

的javadoc中找到一些细节
  

m或a的小数部分必须打印多少位数?必须至少有一个数字来表示小数部分,并且除此之外必须有多个,但只有多少,更多的数字才能唯一地将参数值与double类型的相邻值区分开来。也就是说,假设x是由该方法为有限非零参数d生成的十进制表示所表示的精确数学值。那么d必须是最接近x的double值;或者如果两个double值同样接近x,那么d必须是其中之一,d的有效位的最低有效位必须为0.

答案 3 :(得分:0)

如果它完全基于双打......为什么你使用BigDecimalDouble不会更有意义吗?如果它的价值太大(或太精确)那么......你无法转换它;这就是首先使用BigDecimal的原因。

关于为什么它从javadoc

失去精确度
  

将此BigDecimal转换为double。此转换类似于Java语言规范中定义的从double到float的缩小基元转换:如果此BigDecimal具有太大的幅度表示为double,则它将根据需要转换为Double.NEGATIVE_INFINITY或Double.POSITIVE_INFINITY。请注意,即使返回值是有限的,此转换也可能会丢失有关BigDecimal值精度的信息。

答案 4 :(得分:0)

你已达到双倍的最大精度。如果你仍然希望将值存储在基元中......一种可能的方法是将部分存储在长整数小数点之前

long l = 299792;
double d = 0.457999999984;

由于您没有用完(这是一个错误的单词选择)存储小数部分的精度,您可以为小数部分保留更多的精度数字。这应该很容易做到一些舍入等。