为什么我不能用 pow 函数重现浮点问题?
Why can't I reproduce floating-point issues with pow function?
我最近写了一些类似这样的代码:
// Calculate a ^ b
unsigned int result = 1;
for (unsigned int i = 0; i < b; i++) {
result *= a;
}
我得到的评论是我应该使用 math.h
中的 pow
,我已经准备好指出浮点精度问题,因为 pow
returns一个double
。我什至找到了一个 existing Stack Overflow question 来说明问题。除了当我尝试 运行 来自另一个问题的代码时,它 "worked" 就好了(即 no off-by-1 issues)。
这是我一直在测试的代码:
#include <math.h>
#include <stdio.h>
int main()
{
unsigned int a = 10;
unsigned int result;
for (unsigned int b = 1; b <= 9; b++) {
result = (unsigned int)pow(a, b);
printf("%u\n", result);
}
return 0;
}
下面是我编译和 运行 编译它的方式(在 Ubuntu 18.04.3 和 GCC 版本 7.4.0 上):
$ gcc -O3 -std=c99 -Wall -Wextra -Werror -Wpedantic -pedantic-errors -o pow_test pow_test.c -lm
$ ./pow_test
10
100
1000
10000
100000
1000000
10000000
100000000
1000000000
那么为什么 (unsigned int)pow(a, b)
有效?我假设编译器正在做一些魔术来防止正常的浮点问题?它在做什么,为什么?如果不是所有的编译器都这样做的话,鼓励人们忽视这些问题似乎是个坏主意。如果所有现代编译器都都这样做,这不是您需要担心的问题了吗?
我还看到有人建议使用 (unsigned int)(pow(a, b) + 0.1)
之类的方法来避免 off-by-1 问题。 advantages/disadvantages 与我的循环解决方案相比,advantages/disadvantages 是什么?
pow
有 double
个参数和 return 个值 (cppreference.com). double
has 53 bit significand precision (Wikipedia)。另一方面,示例中的最大值是 1000000000,它有 30 位,因此非常适合 53 位而不会丢失精度。
更新:与 Godbolt 一起玩,结果证明编译器甚至可以优化所有计算,如您的示例 a
和所有 b
值在编译时已知时间,例如 clang
v9.0。使用 -O3
选项只打印常量(gcc
和 msvc
调用 pow
)
这取决于实现。
链接问题中使用的实现显然使用先验函数来实现 pow()
,即使参数是精确整数也是如此。结果,它会出现舍入误差,因此结果有时会略低于实际整数结果。
您正在使用的实现显然会检查参数是否有零分数。在这种情况下,它使用一种算法来生成准确的整数结果(可以通过移动尾数和一些额外的乘法来完成)。只要结果符合 double
的精度,您就会得到一个将转换回精确整数的浮点数。
我最近写了一些类似这样的代码:
// Calculate a ^ b
unsigned int result = 1;
for (unsigned int i = 0; i < b; i++) {
result *= a;
}
我得到的评论是我应该使用 math.h
中的 pow
,我已经准备好指出浮点精度问题,因为 pow
returns一个double
。我什至找到了一个 existing Stack Overflow question 来说明问题。除了当我尝试 运行 来自另一个问题的代码时,它 "worked" 就好了(即 no off-by-1 issues)。
这是我一直在测试的代码:
#include <math.h>
#include <stdio.h>
int main()
{
unsigned int a = 10;
unsigned int result;
for (unsigned int b = 1; b <= 9; b++) {
result = (unsigned int)pow(a, b);
printf("%u\n", result);
}
return 0;
}
下面是我编译和 运行 编译它的方式(在 Ubuntu 18.04.3 和 GCC 版本 7.4.0 上):
$ gcc -O3 -std=c99 -Wall -Wextra -Werror -Wpedantic -pedantic-errors -o pow_test pow_test.c -lm
$ ./pow_test
10
100
1000
10000
100000
1000000
10000000
100000000
1000000000
那么为什么 (unsigned int)pow(a, b)
有效?我假设编译器正在做一些魔术来防止正常的浮点问题?它在做什么,为什么?如果不是所有的编译器都这样做的话,鼓励人们忽视这些问题似乎是个坏主意。如果所有现代编译器都都这样做,这不是您需要担心的问题了吗?
我还看到有人建议使用 (unsigned int)(pow(a, b) + 0.1)
之类的方法来避免 off-by-1 问题。 advantages/disadvantages 与我的循环解决方案相比,advantages/disadvantages 是什么?
pow
有 double
个参数和 return 个值 (cppreference.com). double
has 53 bit significand precision (Wikipedia)。另一方面,示例中的最大值是 1000000000,它有 30 位,因此非常适合 53 位而不会丢失精度。
更新:与 Godbolt 一起玩,结果证明编译器甚至可以优化所有计算,如您的示例 a
和所有 b
值在编译时已知时间,例如 clang
v9.0。使用 -O3
选项只打印常量(gcc
和 msvc
调用 pow
)
这取决于实现。
链接问题中使用的实现显然使用先验函数来实现 pow()
,即使参数是精确整数也是如此。结果,它会出现舍入误差,因此结果有时会略低于实际整数结果。
您正在使用的实现显然会检查参数是否有零分数。在这种情况下,它使用一种算法来生成准确的整数结果(可以通过移动尾数和一些额外的乘法来完成)。只要结果符合 double
的精度,您就会得到一个将转换回精确整数的浮点数。