2

好的,我知道有很多关于 pow 函数并将其结果转换为 int 的问题,但我找不到这个有点具体的问题的答案。

好的,这是 C 代码:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main()
{
    int i = 5;
    int j = 2;

    double d1 = pow(i,j);
    double d2 = pow(5,2);
    int i1 = (int)d1;
    int i2 = (int)d2;
    int i3 = (int)pow(i,j);
    int i4 = (int)pow(5,2);

    printf("%d %d %d %d",i1,i2,i3,i4);

    return 0;
}

这是输出:“25 25 24 25”。请注意,只有在 pow 的参数不是文字的第三种情况下,我们才会得到错误的结果,这可能是由舍入错误引起的。没有明确的强制转换也会发生同样的事情。有人可以解释这四种情况会发生什么吗?

我在 Windows 7 中使用 CodeBlocks,以及随附的 MinGW gcc 编译器。

4

4 回答 4

4

运算的结果pow是 25.0000 加上或减去一些舍入误差。如果舍入误差为正数或零,则转换为整数将产生 25。如果舍入误差为负,则结果为 24。两个答案都是正确的。

内部最有可能发生的情况是,在一种情况下,直接使用更高精度的 80 位 FPU 值,在另一种情况下,将结果从 FPU 写入内存(作为 64 位双精度)并且然后读回(将其转换为稍微不同的 80 位值)。这可以在最终结果中产生微观差异,这就是将 25.0000000001 更改为 24.999999997 所需的全部内容

另一种可能性是您的编译器识别传递给的常量pow并自行进行计算,将结果替换为对pow. 您的编译器可能使用内部任意精度数学库,也可能只使用不同的。

于 2013-06-12T10:29:19.200 回答
2

这是由两个问题的组合引起的:

  • 您正在使用的实施pow质量不高。浮点运算在许多情况下必然是近似的,但良好的实现会注意确保简单的情况,例如pow(5, 2)返回精确的结果。您正在使用返回的pow结果小于 25 的数量大于 0 但小于或等于 2 –49。例如,它可能返回 25–2 -50
  • 您使用的 C 实现有时使用 64 位浮点格式,有时使用 80 位浮点格式。只要数字保持为 80 位格式,它就会保留pow返回的完整值。如果将此值转换为整数,则会产生 24,因为该值小于 25 并且转换为整数会截断;它不圆。当数字转换为 64 位格式时,会进行四舍五入。在浮点格式之间进行转换会舍入,因此结果会舍入到最接近的可表示值 25。之后,转换为整数会产生 25。

只要在某种意义上“方便”,编译器就可以切换格式。例如,80 位格式的寄存器数量有限。当它们已满时,编译器可能会将一些值转换为 64 位格式并将它们存储在内存中。编译器还可能在编译时而不是运行时重新排列表达式或执行其中的一部分,这些会影响执行的算术和使用的格式。

当 C 实现混合浮点格式时会很麻烦,因为用户通常无法预测或控制格式之间的转换何时发生。这会导致结果不易重现,并且会干扰软件的数值属性的导出或控制。C 实现可以设计为始终使用单一格式并避免其中一些问题,但您的 C 实现显然不是这样设计的。

于 2013-06-12T15:35:56.407 回答
1

在此处添加其他答案:在使用浮点值时通常要非常小心。

我强烈推荐阅读这篇论文(尽管它是一个很长的阅读): http ://hal.archives-ouvertes.fr/docs/00/28/14/29/PDF/floating-point-article.pdf

跳到第 3 节查看实际示例,但不要忽略前面的章节!

于 2013-06-12T12:17:43.623 回答
0

我相当确定这可以通过“中间舍入”来解释,而且事实pow并非简单地循环j乘以i,而是使用exp(log(i)*j)浮点计算进行计算。中间舍入很可能将 24.999999999996 转换为 25.000000000 - 即使任意存储和重新加载该值也可能导致这种行为的差异,因此取决于代码的生成方式,它可能会对确切的结果产生影响。

当然,在某些情况下,编译器甚至可能“知道”pow实际实现了什么,并将计算替换为一个恒定的结果。

于 2013-06-12T10:32:15.103 回答