C:计算出的机器 epsilon 与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;。由于循环通过将 f 的值存储在 1 + f != 1 中来记住 prev_f 的值,因此循环结束时 prev_f 的值是导致 {{ 1}} 不等于 1,这是你想要的结果。1

其次,允许 C 实现以比它们的名义类型更精确的方式计算浮点表达式。看来您的 C 实现正在以无限精度有效地评估 1+f(这可以通过在编译时识别 1+f != 1 评估为无限精度是真的,仅当 1+f != 1,因此优化可以将其更改为f != 0)。因此,循环仅在 f != 0 变为零时终止,此时前一个 f 是可表示的最小正值,对于 f 常用的格式是 2-149。 (并且您当前的代码将其加倍并打印 2−148。)请注意,(有效)无限精度仅出现在评估 float 中,而不出现在赋值 1 + f != 1 中。这是因为 C 标准要求实现在执行强制转换和赋值时“丢弃”多余的精度。这为我们提供了解决此问题的方法:将 f /= 2; 更改为 1 + f != 1。这将强制按照 (float) (1 + f) != 1 格式进行评估。2

脚注

1 有点。机器epsilon 有时被错误地表述为最小值x,这样计算1+x 会产生大于x 的值。然而,它被定义为 1 和下一个更大的可表示值之间的差异,比如 1+?。如果我们让 x 略大于 ½?(例如 ½?(1+?)),那么计算 1+x 将产生 1+? 由于四舍五入,即使x 小于机器 epsilon。但是,如果上述问题得到解决并且浮点基数为 2,此代码将找到正确的值,因为它从未测试机器 epsilon 的这些错误候选者之一,因此从未找到。

2 通常,可能会出现双舍入问题:当 C 实现使用超额精度来计算表达式时,它可能会将理想的数学结果舍入到超额精度。当它被强制转换或赋值强制“丢弃”多余的精度时,它会四舍五入到标称精度。这两种四舍五入可能会导致与只有一次四舍五入到标称精度的结果不同的结果。但是,在这种特殊情况下,这不会成为问题。