从java BigDecimal转换为双精度丢失精度为
我正在使用完全基于双精度的应用程序,并且在将字符串解析为双精度的一种实用程序方法中遇到了问题。我发现修复了使用BigDecimal进行转换的问题,但在我将BigDecimal转换回双精度时引发了另一个问题:我失去了几个精度。例如:从java 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 那么双基元的最大指数值为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。
使用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
如果它完全基于双打......你为什么要使用BigDecimal
? Double
会更有意义吗?如果它的值太大(或者精度太高),那么......你不能转换它;这将是首先使用BigDecimal的原因。
至于为什么它失去精度,从javadoc
将此BigDecimal转换为double。这种转换类似于Java语言规范中定义的从double到float的缩小原始转换:如果此BigDecimal具有太大的幅度表示为double,则会根据情况将其转换为Double.NEGATIVE_INFINITY或Double.POSITIVE_INFINITY。请注意,即使返回值是有限的,该转换也会丢失有关BigDecimal值精度的信息。
这适用于OP的问题如何?你有没有看过JLS来了解缩小原始转换的内容? – Anon 2011-04-21 21:10:47
您已达到双倍的最大可能精度。如果你仍想保存在原语的价值......一种可能的方式是因为您没有使用到的部分小数点前存放在长
long l = 299792;
double d = 0.457999999984;
(这词的一个不错的选择)用于存储小数部分的精度,您可以为小数部分保存更多的精度数字。这应该很容易做一些舍入等。
但是一般情况下更好的解决方案是在整个过程中使用'BigDecimal',它旨在用于任意精度数字。 – 2011-05-12 14:02:51
表示同意,尽管不知何故,我得到了OP正在寻找BigDecimal的替代方案的印象。 – 2011-05-13 13:26:25
这会损失大数字的双数的指数范围,并且不会使小数字受益。如果你打算沿着这条路线走下去,那么使用“固定点”(例如,小数部分作为分子超过2^63或2^64)就更有意义了。或者,将该数字表示为双数的总和(即近似值加上一些误差项);有甚至有方法可以用这个方法来做精确的总和(例如在Python的[math.fsum()]中),但我不确定这是多么容易实现乘法/除法。 – 2013-06-10 16:17:14
只有那么多的数字被打印,所以,当解析字符串回到双,它会导致完全相同的值。
某些细节可以在Javadoc中找到Double#toString
多少位必须被打印的米或一个小数部分?必须至少有一位数字来表示小数部分,并且除此之外还需要多少位数,但只需要多少位数来唯一地区分参数值和类型为double的相邻值。也就是说,假设x是由该方法对有限非零参数d产生的十进制表示所表示的精确数学值。那么d必须是最接近x的double值;或者如果两个双值同样接近x,那么d必须它们中的一个和d的有效数字的最低位显著必须为0。
的问题是,一个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());
差不多吧;改变舍入模式,你已经解决了OP的问题 – Anon 2011-04-21 21:12:26
@Anon:你在说什么?没有使用'double'来解决OP的问题。 – WhiteFang34 2011-04-21 21:17:35
查看我的答案,或重读OP的问题,以了解他/她失去9的 – Anon 2011-04-21 21:28:36