C:计算的机器 epsilon 不同于 limits.h
C: Calculated machine epsilon differs from limits.h
我尝试使用以下程序通过简单的 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
存储在 prev_f
中来记住 f
的值,因此循环结束时 prev_f
的值是导致 1+f
不等于1,这就是你想要的结果。1
其次,允许 C 实现以比标称类型更精确的方式计算浮点表达式。看来您的 C 实现正在以无限精度有效地评估 1+f != 1
(这可以通过在编译时识别 1+f != 1
评估为无限精度是真的 iff f != 0
来实现,因此优化可以将其更改为f != 0
)。因此,循环仅在 f
变为零时终止,此时前一个 f
是最小可能的可表示正值,对于 float
常用的格式是 2−149。 (并且您当前的代码将其加倍并打印 2−148。)请注意,(有效的)无限精度仅出现在评估 1 + f != 1
中,而不出现在赋值 [=24= 中].这是因为 C 标准要求实现在执行强制转换和赋值时“丢弃”过高的精度。这为我们提供了解决此问题的方法:将 1 + f != 1
更改为 (float) (1 + f) != 1
。这将强制执行评估,就像在 float
格式中一样。2
脚注
1 差不多吧。机器 episilon 有时被错误地表述为最小值 x,因此计算 1+x 产生的值大于 x 。但是,它被定义为 1 和下一个更大的可表示值(比如 1+)之间的差值。如果我们让 x 略大于 ½(例如 ½(1+)),那么计算 1+x 会产生 1+,因为四舍五入,即使 x 小于机器 epsilon。但是,如果上述问题得到修复并且浮点基数为二,此代码将找到正确的值,因为它从不测试机器 epsilon 的这些错误候选者之一,因此永远找不到一个。
2 通常,会出现双舍入问题:当 C 实现使用超额精度计算表达式时,它可能会将理想的数学结果舍入到超额精度。当强制转换或赋值迫使它“丢弃”多余的精度时,它会四舍五入到标称精度。这两次舍入可能导致与只进行一次舍入以达到标称精度的结果不同的结果。但是,在这种特殊情况下,这不会成为问题。
我尝试使用以下程序通过简单的 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
存储在 prev_f
中来记住 f
的值,因此循环结束时 prev_f
的值是导致 1+f
不等于1,这就是你想要的结果。1
其次,允许 C 实现以比标称类型更精确的方式计算浮点表达式。看来您的 C 实现正在以无限精度有效地评估 1+f != 1
(这可以通过在编译时识别 1+f != 1
评估为无限精度是真的 iff f != 0
来实现,因此优化可以将其更改为f != 0
)。因此,循环仅在 f
变为零时终止,此时前一个 f
是最小可能的可表示正值,对于 float
常用的格式是 2−149。 (并且您当前的代码将其加倍并打印 2−148。)请注意,(有效的)无限精度仅出现在评估 1 + f != 1
中,而不出现在赋值 [=24= 中].这是因为 C 标准要求实现在执行强制转换和赋值时“丢弃”过高的精度。这为我们提供了解决此问题的方法:将 1 + f != 1
更改为 (float) (1 + f) != 1
。这将强制执行评估,就像在 float
格式中一样。2
脚注
1 差不多吧。机器 episilon 有时被错误地表述为最小值 x,因此计算 1+x 产生的值大于 x 。但是,它被定义为 1 和下一个更大的可表示值(比如 1+)之间的差值。如果我们让 x 略大于 ½(例如 ½(1+)),那么计算 1+x 会产生 1+,因为四舍五入,即使 x 小于机器 epsilon。但是,如果上述问题得到修复并且浮点基数为二,此代码将找到正确的值,因为它从不测试机器 epsilon 的这些错误候选者之一,因此永远找不到一个。
2 通常,会出现双舍入问题:当 C 实现使用超额精度计算表达式时,它可能会将理想的数学结果舍入到超额精度。当强制转换或赋值迫使它“丢弃”多余的精度时,它会四舍五入到标称精度。这两次舍入可能导致与只进行一次舍入以达到标称精度的结果不同的结果。但是,在这种特殊情况下,这不会成为问题。