我正在查看一些遗留代码,这些代码沿轴f
定义了一些网格点。
int main(){
double f[9];
int i = 0;
for (double a = 0; a <= 1; a += 0.125){
f[i++] = a;
}
}
我担心重复添加1/8到a
,并且循环未正确运行。这是因为我认为你不能像这样添加浮点值,并且当i
为8时,它依赖于它正好为1。
或者这段代码好吗,我应该停止担心吗? (代码显然至少已有20年历史,并且从未造成过麻烦 - 尽管在原始版本中,double a
在循环之外声明,我正在阅读为什么会这样。)。
答案 0 :(得分:9)
使用C编译器编译时代码很好,C编译器提供精确的IEEE 754语义或它们的近似值(例如,FLT_EVAL_METHOD&gt; 0,或者甚至是子表达式的任意多余精度和任意舍入到标称精度)
你担心的问题是:
0.125
不完全是1/8,而+
不完全是数学加法。对于这个特定的程序,这些都不会发生。0.125要求在基数2中精确表示1位精度。这意味着程序在这些条件下使用的浮点数恰好是预期的1/8。此外,在添加任何近似值之前,它可以加到自身2 53 上。
这种推理对于其他增量步骤不正确。例如,下面的程序的变化留下了一个数组的索引,f[100]
,至少是我的编译器(实现严格的IEEE 754语义)未初始化:
int main(){
double f[101];
int i = 0;
for (double a = 0; a <= 1; a += 0.01){
printf("%.16e %d\n", a, i);
f[i++] = a;
}
}
当我运行它时,我会到达最后一行:
... 9.8000000000000065e-01 98 9.9000000000000066e-01 99
f[100]
永远不会被写入,因为尝试在二进制浮点中重复添加0.01
时会出现表示错误和操作错误。
答案 1 :(得分:-2)
C标准离开浮动数实现行为,很多编译器使用IEEE 754标准。您的代码不正常,因为:
想象一下,有人不知道这个要求,也不用IEEE 754标准进行编译。这种行为可能完全不同。例如,您的代码可能会产生越界,这是未定义的行为,您的程序可能会崩溃。
也就是说,使用IEEE 754标准编译时代码行为相同的代码示例:
#include <stddef.h>
int main(void) {
double f[9];
size_t const size_f = sizeof f / sizeof *f;
double a = 0;
for (size_t i = 0; i < size_f; i++) {
f[i] = a;
a += 0.125;
}
}