如何理解Java中double的这种奇怪行为?[复制]

How to understand this strange behavior of double in Java? [duplicate]

提问人:zhoudu 提问时间:5/26/2023 更新时间:5/26/2023 访问量:88

问:

我试图更深入地理解浮点数。我知道二进制(以 2 为基数)浮点数不能完全表示某些十进制数。但是我对 Java 中 double 的这种奇怪行为感到困惑(在 Java 中,double 是一种二进制浮点数数据类型)。

我写了这段 Java 代码:

public void myTestMethod() {
    double num;
    double factor;
    double compared;
    double result1;
    double result2;
    String output;

    num = 0.3;
    factor = 10;
    compared = 3;

    //num * 10
    result1 = num * factor;

    //add 10 num, which is mathematically equal to num * 10
    result2 = num + num + num + num + num + num + num + num + num + num;

    output = "num: " + num + "\n";
    output = output + "result1: " + result1 + "\n";
    output = output + "result2: " + result2 + "\n";

    if (result1 == compared) {
        output = output + "result1 == compared\n";
    } else {
        output = output + "result1 != compared\n";
    }

    if (result2 == compared) {
        output = output + "result2 == compared\n";
    } else {
        output = output + "result2 != compared\n";
    }

    System.out.print(output);
}

运行此方法会生成以下输出:

num: 0.3
result1: 3.0
result2: 2.9999999999999996
result1 == compared
result2 != compared

据我所知,“factor”的值正好是 10,“compared”的值正好是 3,因为这些整数可以精确地用 double 表示。但也许“num”的值与 0.3 略有不同。

现在我想知道:

  1. “num”的值是 0.3(确切地说)吗?

  2. 如果 “num” 的值是 0.3,那么,为什么 “result2 == compared” 是假的?

  3. 如果“num”的值不是0.3,那么,为什么它的String输出是“0.3”?据我所知,二进制浮点数总是可以精确地用十进制数表示。因此,如果“num”的值不是 0.3,则其 String 输出不应为“0.3”,而应类似于“0.3000000000001”。

  4. 如果 “num” 的值不是 0.3,那么,为什么 “result1 == compared” 为真?

  5. 如果“num”的值不是0.3,那么,它是什么?

Java 双精度浮点

评论

1赞 ΦXocę 웃 Пepeúpa ツ 5/26/2023
永远不要使用“==”比较双基元类型
0赞 fnymmm 5/26/2023
这里有一个有趣的链接给你。它解释了为什么你不能使用 '''==''''
0赞 zhoudu 5/26/2023
是的,我知道在大多数情况下,我不应该使用“==”来表示浮点数,因为它们通常不准确。但在这里我使用“==”,以便更深入地理解浮点数。
6赞 Stephen C 5/26/2023
奇怪的结果是你说你理解的事情的直接后果。如果你想更深入地理解,你需要理解数学。一个起点是:每个计算机科学家都应该知道的关于浮点运算的知识。读一遍,然后再读一遍......直到你明白为止。
2赞 Slaw 5/26/2023
(1) 不可以。调用以查看 的确切值。(2) 对 执行相同的操作。(3) 参见 Double#toString(double) 的 Javadoc。(4) 与问题 1 和问题 2 相同,但对 .(5) 已经回答了。(奖金)阅读 Stephen 提供的链接以了解结果。System.out.println(new BigDecimal(num))numresult2result1

答:

0赞 m0skit0 5/26/2023 #1

由于所有十进制数的范围都是无限的,并且任何数据类型都具有有限的空间,因此不能使用 double 数据类型表示所有可能的十进制数。这就是为什么某些数字将四舍五入到下一个可能的表示形式的原因。

有关实现的更多详细信息,请查看 IEEE 754 标准

8赞 Eric Postpischil 5/26/2023 #2
  1. “num”的值是 0.3(确切地说)吗?

不,它是 0.299999999999999988897769753748434595763683319091796875。

  1. 如果 “num” 的值是 0.3,那么,为什么 “result2 == compared” 是假的?

前提是错误的;的值不是 0.3。此外,使用九次加法进行计算。在每次添加中,都有一个舍入步骤。这些四舍五入的添加结果是 2.9999999999999999555910790149937383830547332763671875。numresult2

  1. 如果“num”的值不是0.3,那么,为什么它的String输出是“0.3”?据我所知,二进制浮点数总是可以精确地用十进制数表示。因此,如果“num”的值不是 0.3,则其 String 输出不应为“0.3”,而应类似于“0.3000000000001”。

Java 规范说,双精度的默认格式是打印足够的十进制数字来唯一区分数字。具体来说,它转换为具有最少数字的十进制数字,其属性是将十进制数字转换回类型会产生原始值。double

将 0.3 转换为产生最接近的可表示值 0.299999999999999988897769753748434595763683319091796875,因此,在打印 0.29999999999999999888897769753748434595763683319091796875 时。使用默认格式时,Java 会生成“0.3”。double

  1. 如果 “num” 的值不是 0.3,那么,为什么 “result1 == compared” 为真?

num是 0.299999999999999988897769753748434595763683319091796875。将其乘以 10 将生成 2.999999999999999988897769753748434595763683319091796875 使用实数算术,但这个数字在 中无法表示。浮点乘法运算生成最接近的值,即 3。doubledouble

  1. 如果“num”的值不是0.3,那么,它是什么?

它是0.29999999999999988897769753748434595763683319091796875。

2赞 Steve Summit 5/26/2023 #3

为了了解这里发生了什么,让我们用十进制做一个类似的问题——也就是说,根本不涉及二进制浮点。我们将使用十进制浮点数,精度限制为三位有效数字。 我们将从值 1/3 开始,然后将其乘以 10,这应该得到 10/3。

但是,当然,我们不能完全用小数表示 1/3。我们能得到的最接近的 3 位有效数字是 0.333。但这已经很接近了。

如果我们取 0.333,然后乘以 10,我们显然得到 3.33。这与 10/3(即 3.33333...)并不完全相同,但同样,它非常接近。

但假设我们取 0.333 并将其添加到自身 10 次。这应该给出相同的答案,对吧?

0.333 + 0.333 + 0.333 是 0.999。到目前为止还好。

0.999 + 0.333 应该是 1.332。但是我们只有三个有效数字的精度。因此,我们将不得不将其四舍五入为1.33。

1.33 + 0.333 应该是 1.663。但是我们只有三个有效数字的精度。因此,我们将不得不将其四舍五入为1.66。

1.66 + 0.333 应该是 1.993。但我们必须将其四舍五入到1.99。

(你开始看到一种模式了吗?情况会变得更糟。

1.99 + 0.333 应为 2.323,四舍五入为 2.32。
2.32 + 0.333 应为 2.653,四舍五入为 2.65。
2.65 + 0.333 应为 2.983,四舍五入为 2.98。
最后,2.98 + 0.333 应该是 3.313,四舍五入为 3.31。

所以 0.333 × 10 是 3.33,但 0.333 + 0.333 + ... + 0.333 是 3.31。为什么我们得到了两个不同的答案?

因为几乎每次我们添加 0.333 时,我们都必须对结果进行四舍五入,并且(在这种情况下)这总是意味着丢弃 0.003。经过七次四舍五入的加法步骤,这些小的四舍五入误差加起来对最终结果产生了重大影响。

当你在二进制中重复添加 0.3 时,或多或少会发生完全相同的事情。0.3 在二进制中并不完全表示,因此您不是每次都添加正好 0.3。您添加的值与 0.3 略有不同,经过 9 次添加后,舍入误差加起来与仅将 0.3 乘以 10 时大不相同。有关更多详细信息,请参阅 Eric Postpischil 的回答