Java DecimalFormat在格式化double时丢失精度

4
当我执行以下代码时:
public class Test {
    public static void main(String args[]){
        DecimalFormat format = new DecimalFormat();
        Double value = new Double(-1350825904190559999913623552.00);

        StringBuffer buffer = new StringBuffer();
        FieldPosition position = new FieldPosition(0);
        format.format(new BigDecimal(value), buffer, position);
        System.out.println(buffer);
    }
}

这段代码正确地输出了-1,350,825,904,190,559,999,913,623,552。 由于我的代码需要处理大量的double,因此我不想将其转换为BigDecimal。我认为BigDecimal的处理时间很长。 所以我使用format.format(value, buffer, position)。 但是我发现精度丢失了。 输出结果为-1,350,825,904,190,560,000,000,000,000
请问我做错了什么?有更好的方法来处理这个问题并保留精度吗?我不想在这里处理BigDecimal,而只想处理小数。
有什么建议吗?

也许发布提供错误结果的代码是有意义的吗? - Iłya Bursov
你确定这真的很重要吗?如果你有Double value = new Double(0.1),你想看到完整的精度(即0.1000000000000000055511151231257827021181583404541015625)吗? - user2357112
@user2357112,我在谈论像1350825904190559999913623552.00这样的大数字。所以,这确实很重要。 - Anusha Pachunuri
@Lashane,代码中唯一的更改是不将其转换为BigDecimal。我将它粘贴到了代码中。 - Anusha Pachunuri
如果你想从double中获得超过15位数字的精度,那么你需要的不仅仅是double所能提供的。我认为你需要BigDecimal,或者你不需要保留完整的精度。 - user2357112
4个回答

2

double类型没有无限精度,而且将double转换为BigDecimal也不能获得比double更高的精度(就像当你使用double r = 1/3;时无法通过将int扩展为double来获得更高的精度,结果是0.0)。相反,您可以使用String类型。例如:

DecimalFormat format = new DecimalFormat();
String value = "-1350825904190559999913623552.00";
System.out.println(format.format(new BigDecimal(value)));

但我认为精度只在格式化期间丢失。如果在创建双精度时丢失了精度,那么为什么转换为BigDecimal会保留精度呢?因此,它不会落入“通过将double转换为BigDecimal,您不能获得比double更高的精度”的情况,对吗? - Anusha Pachunuri
你说了很多“double”类型的数据。0.1+0.2等于多少? - Elliott Frisch

2
问题出在输出格式上,具体来说是默认情况下如何将double转换为字符串。每个double数字都有一个精确的值,但它也是一定范围内十进制分数的字符串转换结果。在这种情况下,该double的精确值为-1350825904190559999913623552,但其范围为[-1350825904190560137352577024, -1350825904190559862474670080]。

Double toString转换从该范围中选择具有最少有效数字的数字,即-1.35082590419056E27。该字符串确实可以转换回原始值。

如果您真的想看到确切的值,而不仅仅是足以唯一标识double的位数,请使用您当前的BigDecimal方法。

以下是我用于计算此答案中数字的程序:

import java.math.BigDecimal;

public class Test {
  public static void main(String args[]) {
    double value = -1350825904190559999913623552.00;
    /* Get an exact printout of the double by conversion to BigDecimal
     * followed by BigDecimal output. Both those operations are exact.
     */
    BigDecimal bdValue = new BigDecimal(value);
    System.out.println("Exact value: " + bdValue);
    /* Determine whether the range is open or closed. The half way
     * points round to even, so they are included in the range for a number
     * with an even significand, but not for one with an odd significand.
     */
    boolean isEven = (Double.doubleToLongBits(value) & 1) == 0;
    /* Find the lower bound of the range, by taking the mean, in
     * BigDecimal arithmetic for exactness, of the value and the next
     * exactly representable value in the negative infinity direction.
     */
    BigDecimal nextDown = new BigDecimal(Math.nextAfter(value,
        Double.NEGATIVE_INFINITY));
    BigDecimal lowerBound = bdValue.add(nextDown).divide(BigDecimal.valueOf(2));
    /* Similarly, find the upper bound of the range by going in the
     * positive infinity direction.
     */
    BigDecimal nextUp = new BigDecimal(Math.nextAfter(value,
        Double.POSITIVE_INFINITY));
    BigDecimal upperBound = bdValue.add(nextUp).divide(BigDecimal.valueOf(2));
    /* Output the range, with [] if closed, () if open.*/
    System.out.println("Range: " + (isEven ? "[" : "(") + lowerBound + ","
        + upperBound + (isEven ? "]" : ")"));
    /* Output the result of applying Double's toString to the value.*/
    String valueString = Double.toString(value);
    System.out.println("toString result: " + valueString);
    /* And use BigDecimal as above to print the exact value of the result
     * of converting the toString result back again.
     */
    System.out.println("exact value of toString result as double: "
        + new BigDecimal(Double.parseDouble(valueString)));
  }
}

输出:

Exact value: -1350825904190559999913623552
Range: [-1350825904190560137352577024,-1350825904190559862474670080]
toString result: -1.35082590419056E27
exact value of toString result as double: -1350825904190559999913623552

我喜欢你的解释。但是对于我来说,这个程序有点混乱。所以我有两个问题:1)这个范围[-1350825904190560137352577024,-1350825904190559862474670080]是什么?2)从你的话中可以得出,将double格式化在这种情况下只显示足以唯一标识它的数字?即使double在2 ^ 53和Double.MAX_VALUE的范围内,格式化也无法正确地容纳所有数字? - Anusha Pachunuri
抱歉,我想我明白了!这是一个很好的解释。我被你指定的十进制小数范围的字符串转换为双精度浮点数的方式所吸引。有更好的阅读链接吗? - Anusha Pachunuri
JLS是指Java.lang.Double的API文档valueOf,用于将字符串转换为双精度浮点数。特别地,“这个确切的数值被概念上转换为一个“无限精确”的二进制值,然后按照IEEE 754浮点算术的通常四舍五入规则舍入为double类型”。 - Patricia Shanahan

1
在格式化过程中它并没有丢失,而是在这里丢失了:
Double value = new Double(-1350825904190559999913623552.00);

double只有大约15.9个有效十进制数字。它不适合。当浮点字面量被转换时,编译时发生了精度损失。


0

你无法用 Double 准确表示 1350825904190559999913623552.00。如果你想知道为什么,请阅读这个 文章

如果你想表示这个值,我建议使用你在问题中使用的代码:new BigDecimal( value ),其中 value 实际上是一个 String 表示。


在2^53和Double.MAX_VALUE之间,有一些数字是可以精确表示的,但并非全部。而这似乎就是其中之一。 - Patricia Shanahan
http://en.wikipedia.org/wiki/Floating_point#IEEE_754%3a_floating_point_in_modern_computers - Zyn
1
我熟悉那个网页,并且其中没有任何与某些大数确切可表示的主张相矛盾的内容,而这就是其中之一。你可以使用十进制到浮点转换器进行验证。 - Patricia Shanahan
@Patricia - 你的意思是这个数字可以表示为double类型,而且不会因为转换成double类型而失去精度,只是在格式化时可能会有问题。是这样吗? - Anusha Pachunuri

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接