为什么在浮点数相乘时没有错误?

3
我希望你能为我提供关于浮点数运算的解释。以下是我编写的一些学习用的代码:
#include "stdio.h"

int main (int argc, char const *argv[])
{
    int i;
    double a=1.0/10.0;
    double sum=0;

    for(i = 0; i < 10; ++i)
        sum+=a;

    printf("%.17G\n", 10*a );
    printf("%d\n", (10*a == 1.0) );

    printf("%.17G\n", sum );
    printf("%d\n", (sum == 1.0) );

    return 0;
}

它输出的结果是:

    1
    1
    0.99999999999999989
    0

为什么 (sum == 1.0) 是假的很容易理解,但是为什么乘法可以给出正确答案而没有错误呢?

谢谢。

3个回答

2

如果您查看实际生成的汇编语言,您会发现编译器未生成您所要求的单个乘法。相反,它只是提供了实际的值。如果关闭优化,则可能会得到您期望的结果(除非您的编译器无论如何都会优化此项)。


这将是一个错误的优化,所以编译器不应该进行这样的优化,除非在非符合标准的配置文件中... - R.. GitHub STOP HELPING ICE

1
在执行重复加法时,会发生9次取整:第一个结果a+a总是可以精确表示的,但之后的加法是不精确的,因为加数的二进制指数不相等。
在执行乘法时,只有一次取整,并且它恰好给出了您想要的结果。

实际上,“a + a + a + a”除了溢出情况外,总是恰好等于“4 * a”(这个小技巧来自Stephen Canon)。 - Pascal Cuoq
@PascalCuoq:不,这不是这样的。(a+a)+(a+a)恰好等于4*a。但浮点数加法不满足结合律。该说法在默认舍入模式(最近舍入/向偶数舍入)下可能是正确的,因为有关四舍五入的论点,但一般情况下肯定不正确。 - R.. GitHub STOP HELPING ICE
这个评论显然是在IEEE 754规定的二进制格式上下文中,采用最近偶数舍入。如果你要挑刺,你的解释假设了一个二进制格式,而问题中没有任何证据支持这一点。 - Pascal Cuoq
IEEE 754有多种舍入模式,并为所有这些模式指定行为,因此在这种情况下,我反对假设默认模式确实是有道理的。至少应该注意到行为取决于舍入模式,否则该声明看起来显然是错误的。 - R.. GitHub STOP HELPING ICE
1
Re 并且它恰好给出了你想要的结果 -- 这是关键。在我的电脑上,使用默认舍入方式,有许多数字使得(1.0/x)*x不等于1.0。(例如,x=49.0)。最好将浮点数视为实数的粗略近似。即使是能够进行无限精度算术运算的理想图灵机也会在实数方面遇到问题,因为几乎所有的实数都不是可计算数。 - David Hammen

0

你的代码存在很多问题。

计算机上的浮点数是基于2进制的。你不能精确地表示0.1(就像你无法在10进制中精确地表示1/3一样),所以在此之后做出的任何其他假设(例如乘以10并期望它是1)都是错误的。

单次乘以10和0.1只会产生一倍的误差。多次加法会产生10倍的误差。舍入修复了10次0.1转换为整数时的误差,使其看起来好像真的起作用了。IEEE浮点数的另一个特性是舍入模式,系统默认使用的舍入模式以及1/10变成的内容再次使单次乘法看起来像是有效的。

下一个问题是你正在使用浮点数进行等号比较,并且我认为有某种期望。不要对浮点数使用等号,无论如何都不要这样做。特别是对于像这样无法在浮点数中精确表示的数字。

尝试使用1/16或1/8这样的数字,而不是1/10...


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