试图编写代码来查找机器epsilon

时间:2010-08-13 16:14:40

标签: c floating-point double floating-accuracy

我试图找出C中各种浮点格式的精度等级(即float,double和long double)。这是我目前正在使用的代码:

#include <stdio.h>
#define N 100000

int main(void)
{
   float max = 1.0, min = 0.0, test;
   int i;                              /* Counter for the conditional loop */

   for (i = 0; i < N; i++) {
      test = (max + min) / 2.0;
      if( (1.0 + test) != 1.0)         /* If too high, set max to test and try again */
     max = test;
  if( (1.0 + test) == 1.0)     /* If too low, set min to test and try again */
         min = test;
   }
   printf("The epsilon machine is %.50lf\n", max);
   return 0;
}

这给出了大约~2 ^ -64的值,如预期的那样。然而,当我将减速度改为双打或“长双打”时,我得到相同的答案我应该得到一个较小的值,但我没有。有人有任何想法吗?

6 个答案:

答案 0 :(得分:9)

这取决于你所说的“精确等级”。

浮点数具有“常规”(正常)值,但也有特殊的次正常数字。如果要查找不同的限制,C标准具有预定义的常量:

#include <math.h>
#include <stdio.h>
#include <float.h>

int main(void)
{
    printf("%30s: %g\n", "FLT_EPSILON", FLT_EPSILON);
    printf("%30s: %g\n", "FLT_MIN", FLT_MIN);
    printf("%30s: %g\n", "nextafterf(0.0, 1.0)", nextafterf(0.0, 1.0));
    printf("%30s: %g\n", "nextafterf(1.0, 2.0)-1", (nextafterf(1.0, 2.0) - 1.0f));
    puts("");
    printf("%30s: %g\n", "DBL_EPSILON", DBL_EPSILON);
    printf("%30s: %g\n", "DBL_MIN", DBL_MIN);
    printf("%30s: %g\n", "nextafter(0.0, 1.0)", nextafter(0.0, 1.0));
    printf("%30s: %g\n", "nextafter(1.0, 2.0)-1", (nextafter(1.0, 2.0) - 1.0));
    puts("");
    printf("%30s: %Lg\n", "LDBL_EPSILON", LDBL_EPSILON);
    printf("%30s: %Lg\n", "LDBL_MIN", LDBL_MIN);
    printf("%30s: %Lg\n", "nextafterl(0.0, 1.0)", nextafterl(0.0, 1.0));
    printf("%30s: %Lg\n", "nextafterl(1.0, 2.0)-1", (nextafterl(1.0, 2.0) - 1.0));
    return 0;
}

上述程序为每种类型打印4个值:

  • 该类型中1和最小值之间的差异大于1( TYPE _EPSILON),
  • 给定类型中的最小正标准化值( TYPE _MIN)。这不包括subnormal numbers
  • 给定类型中的最小正值(nextafter * (0 ... ))。这包括次正规数,
  • 大于1的最小数字。这与 TYPE _EPSILON相同,但以不同的方式计算。

根据“精确度”的含义,上述任何一种或全部都不适合您。

以下是我计算机上的上述程序的输出:

               FLT_EPSILON: 1.19209e-07
                   FLT_MIN: 1.17549e-38
      nextafterf(0.0, 1.0): 1.4013e-45
    nextafterf(1.0, 2.0)-1: 1.19209e-07

               DBL_EPSILON: 2.22045e-16
                   DBL_MIN: 2.22507e-308
       nextafter(0.0, 1.0): 4.94066e-324
     nextafter(1.0, 2.0)-1: 2.22045e-16

              LDBL_EPSILON: 1.0842e-19
                  LDBL_MIN: 3.3621e-4932
      nextafterl(0.0, 1.0): 3.6452e-4951
    nextafterl(1.0, 2.0)-1: 1.0842e-19

答案 1 :(得分:2)

猜猜为什么你会得到同样的答案:

if( (1.0 + test) != 1.0)

这里1.0是一个双常数,所以它将你的浮点数提升为一个双精度数,然后再加上一个双精度数。你可能想在这里声明一个临时浮点数来执行加法,或者使这些浮点数字常量(1.0f IIRC)。

您可能也会陷入临时精度过高的浮动问题,可能需要强制它将中间体存储在内存中以降低到正确的精度。


这里可以快速重做您的范围搜索方法,但计算出正确类型的测试。不过,我得到了一个稍微过大的答案。

#include <stdio.h>
#define N 100000
#define TYPE float

int main(void)
{
   TYPE max = 1.0, min = 0.0, test;
   int i;

   for (i = 0; i < N; i++)
   {
      TYPE one_plus_test;

      test = (max + min) / ((TYPE)2.0);
      one_plus_test = ((TYPE)1.0) + test;
      if (one_plus_test == ((TYPE)1.0))
      {
         min = test;
      }
      else
      {
         max = test;
      }
   }
   printf("The epsilon machine is %.50lf\n", max);
   return 0;
}

答案 2 :(得分:2)

我不确定你的算法应该如何工作。这个(C ++)给出了正确的答案:

#include <iostream>

template<typename T>
int epsilon() {
    int pow = 0;
    T eps = 1;
    while (eps + 1 != 1) {
        eps /= 2;
        --pow;
    }
    return pow + 1;
}

int main() {
    std::cout << "Epsilon for float: 2^" << epsilon<float>() << '\n';
    std::cout << "Epsilon for double: 2^" << epsilon<double>() << '\n';
}

这会计算最小值,以便在添加到1时仍可与1区分开。

输出:

Epsilon for float: 2^-23
Epsilon for double: 2^-52

答案 3 :(得分:2)

IEEE 754浮点格式具有以下特性:当重新解释为相同宽度的二进制补码整数时,它们在正值上单调递增,在负值上单调递减(参见32位浮点数的二进制表示)。它们还具有0 <0的特性。 | F(X)| &LT; ∞,| f(x + 1) - f(x)| ≥| f(x) - f(x-1)| (其中f(x)是上述x)的整数重新解释。在允许类型惩罚并且始终使用IEEE 754-1985的语言中,我们可以利用它来在恒定时间内计算机器epsilon。例如,在C:

typedef union {
  long long i64;
  double d64;
} dbl_64;

double machine_eps (double value)
{
    dbl_64 s;
    s.d64 = value;
    s.i64++;
    return s.d64 - value;
}

来自https://en.wikipedia.org/wiki/Machine_epsilon

答案 4 :(得分:1)

我想补充一点,您可以使用long double从浮点计算中获得最高精度。

要将其应用于@ Rup的解决方案,只需将TYPE更改为long double,将printf语句更改为:

printf("The epsilon machine is %.50Lf\n", max);

这是我的机器上的Epsilon使用float

0.00000005960465188081798260100185871124267578125000

使用long double

0.00000000000000000005421010862427522170625011179761

差异非常显着。

答案 5 :(得分:0)

此类代码存在的问题是编译器会将浮点变量加载到微处理器的浮点寄存器中。如果您的微处理器只有双精度浮点寄存器,则floatdouble的精度将相同。

您需要找到一种方法来强制编译器在每两次计算之间将浮点值存储回内存(转换为正确类型的变量)。这样就必须抛弃寄存器的额外精度。但今天的编译器在优化代码方面非常聪明。所以这很难实现。