【问题标题】:is (0.1 + 0.2) == 0.3 true or false?是 (0.1 + 0.2) == 0.3 对还是错?
【发布时间】:2020-10-24 20:46:08
【问题描述】:

我对浮点数有基本的了解,并且正在阅读 this article 上面写着:

0.1 + 0.2:这等于 0.3,但在浮点数中:(0.1 + 0.2) == 0.3 为假。这是因为 0.1、0.2 和 0.3 不能以 2 为底的浮点数精确表示。

嗯,根据浮点数的性质确实是这样,不过我写了一个简单的程序来测试:

float a = 0.1;
float b = 0.2;

if(a+b == 0.3)
{
  printf("true");
} else 
{
  printf("false");
}
// result is true

但输出实际上是true。这是我的两个问题:

  1. 我想会发生什么,因为C使用的是round-to-even舍入模式,所以在四舍五入之后,恰好是真的,我的理解正确吗?

  2. 如果我的理解是正确的,那么在这种情况下一定有一些指定的浮点数不会是真的,因为舍入失败的可能性仍然很小。所以这一定是某种组合

    float a = ...;
    float b = ...;
    if(a+b == XXX)  // where XXX is the "intuitive" sum of a and b
    {
      printf("true");
    } else 
    {
      printf("false");   
    }
    
    //result is false now
    

我的理解正确吗?

【问题讨论】:

  • 你用的是什么编译器?我得到false 用于上面使用 gcc 的第一个代码块。 (可能是因为 0.3double 文字。)
  • printf("%d\n", (int)(sizeof (double) - sizeof (float)));
  • 只是为了澄清(除了 DevSolar 的评论),舍入模式在这里大多是无关紧要的,而且是红鲱鱼;在这种情况下,任何其他舍入模式都会显示相同的行为,因为float 的精度太小而无法表达0.1f + 0.2f0.3f 之间的差异(甚至在舍入之前!)。无论如何,舍入失败的可能性不是“小”:有 无限多 数字,float 算术会给你错误的结果(与有限精度的无限多的情况相同十进制浮点运算会产生错误的结果)。

标签: c floating-point bit-manipulation


【解决方案1】:

我为您的程序收到false,而不是您在问题中指出的true。 (0.3double 文字,所以我的猜测是,当您在本地测试时,您使用了 float 变量而不是 0.3 文字。)如果您实际使用 float== 0.3f 代替== 0.3),你会得到 true 作为输出,因为碰巧float0.1 + 0.2 == 0.3 是真的。

但是,基本点仍然是 float(单精度)和 double(双精度)使用的 IEEE-754 binary floating-point 计算速度非常快,并且对很多事物,但对于某些值而言本质上是不精确的,因此您会遇到这种问题。使用float,对于0.1 + 0.2 == 0.3,您将获得true,但对于0.1 + 0.6 == 0.7,您将获得false

#include <stdio.h>

int main() {
    printf("%s\n", 0.1f + 0.6f == 0.7f ? "true" : "false"); // prints false
    //             ^^^^−−−^^^^−−−−^^^^−−− `float` literals
    return 0;
}

这个问题的著名0.1 + 0.2 == 0.3版本恰好发生在double

#include <stdio.h>

int main() {
    printf("%s\n", 0.1 + 0.2 == 0.3 ? "true" : "false"); // prints false
    //             ^^^−−−^^^−−−−^^^−−− `double` literals
    return 0;
}

【讨论】:

  • Crowder 感谢您的出色回答,是的,我在本地使用 0.3 作为浮点变量,现在我明白了。顺便说一句,为什么浮点数的计算速度真的很快?
  • @slowjams - 因为它们就是这样设计的。 Wikipedia article 可能有更多细节。
【解决方案2】:

通常,所有float 右值都声明为double(例如0.5、11.332、8.9 等)因此,当您编写以下语句时:

float a = 0.1;
float b = 0.2;

if(a+b == 0.3)
  printf("true");
else
  printf("false");

它的计算结果是 0.1 + 0.2 = 0.3,这没关系,但在右侧,0.3 并不像您期望的那样工作,正如已经提到的,它默认声明为 double

因此,编译器尝试比较该值:

0.3 == 0.2999999999999999889

这显然不相等。

要解决此问题,您需要附加一个后缀来表示您尝试使用浮点值的编译器。

试试这个:

(a + b == 0.3F)

Ff 表示该值为float

很遗憾,您不能用double 做同样的事情。为了证明这一点,可以编写如下代码:

#include <iomanip>
.
.
.
cout << setprecision(20) << 0.1 << endl;
cout << setprecision(20) << 0.2 << endl;
cout << setprecision(20) << 0.3 << endl;
cout << setprecision(20) << (0.1 + 0.2) << endl;

您会知道以上所有将显示的值都有不同的值:

0.10000000000000000555 // 0.1
0.2000000000000000111  // 0.2
0.2999999999999999889  // 0.3 -------- NOTE
0.30000000000000004441 // 0.1 + 0.2 -- NOTE

现在,比较NOTE 的值。它们也是不平等的。

因此,0.2999999999999999889 和 0.30000000000000004441 的比较失败,您不惜一切代价得到 False。

【讨论】:

    【解决方案3】:

    在数学意义上,0.1 + 0.2 == 0.3 总是正确的

    在浮点意义上,0.1 + 0.2 == 0.3 仅在 error in representing 0.1 + error in representing 0.2 == error in representing 0.3 时为真

    我相信您知道错误取决于值及其大小。因此,在少数情况下错误对齐使得浮点数似乎适用于相等,但一般情况是这种比较通常是错误的,因为它们无法解释错误。

    要编写强大的浮点代码,您需要研究测量理论,以及如何在整个公式中传播测量误差。这也意味着您必须将 C 类型(位比较)相等替换为“在错误范围内相等”。

    请注意,您无法构建一个完美地自动处理程序中的错误的系统,因为这样做需要一个有限大小的精确存储方法来存储任何可能无限重复数字的小数。因此,通常会使用误差估计,并且通常会在针对所涉及的值进行调整的近似边界内比较结果。

    很快就会意识到,虽然您的程序是正确的,但您不能信任该技术,因为该技术通常不会返回正确的值。

    【讨论】:

    • 表示0.3的误差不等于表示0.1和0.2的误差之和。需要另一个误差,即表示两个近似值之和的误差。用单精度浮点数表示 0.1 0.2 和 0.3 的误差分别为 1/(5*2^27) 、 2/(5*2^27) 和 1/(5*2^24)。表示 (0.1f+0.2f) 的误差确实是 1/(2^27)...
    • 一个典型的例子是双精度的 0.1+0.4==0.5,虽然两个操作数都有表示错误(都是过量的),但它们的总和似乎等于 1/2,根本没有表示错误。
    • @aka.nice 我并没有考虑误差的大小,而是与理想数字的绝对距离。因此,如果一个错误是正面的,另一个是负面的,并且最终结果可以没有任何错误,则正面和负面错误必须相等才能达到最终结果。希望这能澄清我的陈述,但通常 (a + a.error) + (b + b.error) == (c + c.error) 当 (a + b == c) 意味着 (a.error + b .error = c.error) 否则它不是一个线性系统。
    • 当心,这里你正在处理浮点加法,它可能在最后带有自己的舍入。尽管 0.1 和 0.4 分别大于 1/10 和 4/10,但在四舍五入到最近的浮点数后它们的总和为 0.5...
    • 因此公式为 (a + a.error) + (b + b.error) + sum.error == (c + c.error) 忘记 sum.error 会导致误解。
    猜你喜欢
    • 2021-11-04
    • 2011-10-16
    • 2020-02-15
    • 1970-01-01
    • 1970-01-01
    • 2013-03-10
    • 1970-01-01
    • 1970-01-01
    • 2012-01-26
    相关资源
    最近更新 更多