Java DecimalFormat 在格式化双精度时失去精度

Posted

技术标签:

【中文标题】Java DecimalFormat 在格式化双精度时失去精度【英文标题】:Java DecimalFormat losing precision while formatting double 【发布时间】:2015-04-16 23:26:26 【问题描述】:

当我执行以下代码时:

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。 我的代码确实经历了很多双精度数,所以我不希望从双精度数转换为大十进制数。我认为 BigDecimal 的处理时间很长。 所以我做format.format(value, buffer, position) 我看到精度丢失了。 我得到的输出是-1,350,825,904,190,560,000,000,000,000

我在这里做错了什么?有没有更好的方法来处理这个问题并且仍然保持精度。我不想在这里处理 BigDecimals,而只想处理小数。

有什么建议吗?

【问题讨论】:

也许发布也提供不正确结果的代码是有意义的? 你确定这真的很重要吗?如果您有Double value = new Double(0.1),您想查看完整的精度(即0.1000000000000000055511151231257827021181583404541015625)吗? @user2357112 嗯,我在这里谈论像 1350825904190559999913623552.00 这样的大数字。所以是的,这很重要。 @Lashane 代码中唯一的变化不是转换为大十进制。我把它粘贴在代码中 @AnushaPachunuri:如果你想要一个双精度超过 15 位的精度,那么你需要的不仅仅是双精度。我认为要么你需要 BigDecimal,要么你不需要保持完整的精度。 【参考方案1】:

double 没有无限精度,您无法通过将 double 转换为 BigDecimal 获得比 double 更高的精度(就像使用 @ 无法获得更高的精度一样987654325@ 当您执行 double r = 1/3; 时是 0.0,因为它 int 扩展double)。相反,您可以使用String。类似的东西

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

【讨论】:

但我认为精度只会在格式化过程中丢失。如果在创建双精度时丢失了精度,为什么要转换为 bigdecimal 来保留它。因此,它与“通过将双精度转换为 BigDecimal 无法获得比双精度更高的精度”不是同一种情况,是吗? 你说了很多“双打”。什么是 0.1+0.2?【参考方案2】:

在格式化过程中不会丢失。它就在这里丢失了:

Double value = new Double(-1350825904190559999913623552.00);

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

【讨论】:

【参考方案3】:

问题在于输出格式,特别是默认情况下如何将双精度转换为字符串。每个双精度数都有一个精确值,但它也是一系列小数的字符串到双精度转换的结果。在这种情况下,双精度的确切值为 -1350825904190559999913623552,但范围是 [-1350825904190560137352577024,-1350825904190559862474670080]。

Double toString 转换从该范围内挑选有效数字最少的数字 -1.35082590419056E27。该字符串确实转换回原始值。

如果您真的想看到确切的值,而不是仅仅有足够的数字来唯一标识双精度,那么您当前的 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)从你所说的,在这种情况下格式化双精度只显示足够的数字从范围中唯一识别它?即使双精度在 2^53 和 Double.MAX_VALUE 的范围内,格式也不能容纳所有正确的数字? 对不起,我想我明白了!这是一个很好的解释。我对您指定的小数范围的字符串到双倍转换的事实很感兴趣。有没有更好的链接来阅读这个? JLS 参考 java.lang.Double valueOf 的 API 文档,用于字符串到双精度转换。特别是“这个精确的数值然后在概念上被转换为一个“无限精确”的二进制值,然后通过 IEEE 754 浮点算术的通常的舍入到最近的规则将其舍入为 double 类型。【参考方案4】:

您不能用Double 准确地表示 1350825904190559999913623552.00。如果您想知道原因,请浏览此article。

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

【讨论】:

2^53 和 Double.MAX_VALUE 之间的一些(但不是全部)数字是完全可表示的,在我看来这就是其中之一。 en.wikipedia.org/wiki/… 我对那个网页很熟悉,其中没有什么与某些大数可以精确表示的说法相矛盾,这就是其中之一。您可以使用Decimal to Floating-Point Converter 进行检查 @Patricia - 所以你说这个数字可以表示为一个双精度数,并且在格式化它时不会失去它的精度。对吗?

以上是关于Java DecimalFormat 在格式化双精度时失去精度的主要内容,如果未能解决你的问题,请参考以下文章

(转)Java DecimalFormat 用法(数字格式化)

Java 详解数字格式化(NumberFormat&DecimalFormat)

Java DecimalFormat 在格式化双精度时失去精度

Java DecimalFormat 用法(数字格式化)

java 使用DecimalFormat进行数字的格式化实例详解

JAVA_DecimalFormat数字格式化