我尝试使用以下程序通过简单的 C 程序来估计机器 epsilon
#include <stdio.h>
#include <limits.h>
#include <float.h>
int main(){
float f = 1.0f;
float prev_f = 1.0f;
while( 1 + f != 1 ){
prev_f = f;
f /= 2;
}
prev_f *= 2;
printf("Calculated epsilon for float is %.10g", prev_f);
printf("The actual value is %.10f", FLT_EPSILON);
return 0;
}
我的输出在哪里
Calculated epsilon for float is 2.802596929e-45
The actual value is 0.0000001192
谁能给我解释一下这个偏差吗?它是特定于架构的吗?编译器依赖?我做错了什么吗?
编辑: 这个问题似乎是由于我在 gcc 中使用 -Ofast 优化造成的。使用 -O 代替可以解决问题。
最佳答案
首先,删除 prev_f *= 2;
。由于循环通过将 1 + f != 1
失败的 f
值存储在 prev_f
中来记住 f
的值,因此 的值循环结束时的 prev_f
是导致 1+f
不等于 1 的最后一个值,这就是您想要的结果。1
其次,C 实现可以比其标称类型更精确地计算浮点表达式。看来您的 C 实现正在以无限精度有效地评估 1+f != 1
(这可以通过在编译时识别 1+f != 1
评估为无限来实现)当且仅当 f != 0
时精度为 true,因此优化可以将其更改为 f != 0
)。因此,循环仅在 f
变为零时终止,此时前一个 f
是最小的可表示正值,对于 常用的格式 float
为2−149。 (您当前的代码将其加倍并打印 2−148。)请注意,(有效)无限精度仅出现在计算 1 + f != 1
时,而不是在计算 1 + f != 1
时出现。赋值f/= 2;
。这是因为 C 标准要求实现在执行强制转换和赋值时“丢弃”多余的精度。这给了我们这个问题的解决方案:将 1 + f != 1
更改为 (float) (1 + f) != 1
。这将强制以 float
格式进行计算。2
脚注
1 有点。机器 epsilon 有时被错误地表述为最小值 x,因此计算 1+x 会产生大于 x 的值。然而,它被定义为 1 和下一个更大的可表示值(例如 1+𝜀)之间的差值。如果我们让 x 略大于 ½𝜀(例如 ½𝜀(1+𝜀)),那么计算 1+x 将由于舍入而产生 1+𝜀,即使x 小于机器 epsilon。但是,如果上述问题得到解决并且浮点基数为 2,此代码将找到正确的值,因为它从未测试机器 epsilon 的这些错误候选者之一,因此永远找不到一个。
2 通常,可能会出现双舍入问题:当 C 实现使用超额精度来计算表达式时,它可能会将理想的数学结果舍入到该超额精度。当它通过强制转换或赋值而被迫“丢弃”多余的精度时,它会舍入到标称精度。这两次舍入可能会导致与仅一次舍入到标称精度时不同的结果。但是,在本例中这不会成为问题。
关于C:计算出的机器 epsilon 与 limit.h 不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66232786/