在典型浮点中没有倒数的最小正整数是多少?

时间:2012-11-26 06:39:49

标签: language-agnostic floating-point floating-accuracy ieee-754

一个常见的假设是1 / x * x == 1。在符合IEEE 754标准的通用硬件上,最小正整数是什么?

当乘法逆的假设失败时,写得不好的理性算术就不再起作用了。因为默认情况下包括C和C ++在内的许多语言都将浮点数转换为使用舍入为零的整数,所以即使很小的错误也会导致整数结果被一个错误。

快速测试程序会产生各种结果。

#include <iostream>

int main () {
    {
        double n;
        for ( n = 2; 1 / n * n == 1; ++ n ) ;
        std::cout << n << " (" << 1 - 1/n*n << ")\n";
        for ( ; (int) ( 1 / n * n ) == 1; ++ n ) ;
        std::cout << n << " (" << 1 - 1/n*n << ")\n";
    }
    {
        float n;
        for ( n = 2; 1 / n * n == 1; ++ n ) ;
        std::cout << n << " (" << 1 - 1/n*n << ")\n";
        for ( ; (int) ( 1 / n * n ) == 1; ++ n ) ;
        std::cout << n << " (" << 1 - 1/n*n << ")\n";
    }
}

使用GCC 4.3.4在ideone.com上,结果为

41 (5.42101e-20)
45 (5.42101e-20)
41 (5.42101e-20)
45 (5.42101e-20)

使用GCC 4.5.1会产生相同的结果,但报告的误差范围恰好为零。

在我的机器上(GCC 4.7.2或Clang 4.1),结果是

49 (1.11022e-16)
49 (1.11022e-16)
41 (5.96046e-08)
41 (5.96046e-08)

这与--fast-math选项无关。令人惊讶地使用-mfpmath=387生成

41 (5.42101e-20)
41 (5.42101e-20)
41 (5.42101e-20)
41 (5.42101e-20)

值5×10 -20 似乎意味着epsilon对应于64位尾数,即使用Intel 80位扩展精度的内部计算。

这似乎高度依赖于FPU硬件。有可靠的价值对测试有好处吗?

注意:我不关心语言标准或编译器对浮点数系统的保证,尽管我认为在任何通用编程系统中都没有很多有意义的保证。我想知道数字和现实世界计算机之间的相互作用。

2 个答案:

答案 0 :(得分:4)

双精度:

1/41 = 0x1.8f9c18f9c18fap-6,41 * 0x1.8f9c18f9c18fap-6 = 0x1.000000000000028,其舍入为1。 1/45 = 0x1.6c16c16c16c17p-6和45 * 0x1.6c16c16c16c17p-6 = 0x1.00000000000002c,舍入为1.

然而,

1/49 = 0x1.4e5e0a72f0539p-6,和49 * 0x1.4e5e0a72f0539p-6 = 0x0.fffffffffffffa4,其舍入为0x0.fffffffffffff8 = 0x1.fffffffffffff0p-1

但是,49确实有倒数!它是0x1.4e5e0a72f053ap-6。

更一般地说,如果f是[1,2]中的浮点数,则f具有倒数。在通常的舍入到偶数算术中,如果数字位于[1 - 2 -54 ,1 + 2 -53 ],则数字将舍入为1。 请注意,距离1 / f最近的双倍(比如d)小于2 -54 远离1 / f。如果d> 1 / f,那我们是金色的; 1&lt; f * d&lt; f *(1 / f + 2 -54 )&lt; = 1 + 2 -54 * f&lt; 1 + 2 -53 ,因此f * d舍入为1.如果d <1。 1 / f,然后f * d可以舍入到1 - 2 -53 。如果是,则f * d位于[1-2 - sup> -53 ,1 - 2 -54 )。如果你取e = 2 -53 + d,那么e * f> 1和e * f = d * f + 2 -53 * f&lt; 1 - 2 -53 + 2 -52 = 1 + 2 -53 ,再次舍入为1.

编辑:上述推理错误,因为两个连续双打之间的步幅偏差了两倍。不具有倒数的double的示例是0x1.ffffffbfffffe。 0x1.0000002000001p-1太小但0x1.0000002000002p-1太大。不具有倒数的整数的最小示例是237. 1/237大约是0x1.1485f0e0acd3B68c6Bp-8,其舍入为0x1.1485f0e0acd58p-8。这个数字太小了,而下一个数字太大了。

答案 1 :(得分:0)

这个问题似乎与C ++选择转换为整数的方法有关。

这是一个用于比较的Ada版本,测试32位,64位和80位浮点数(只需要7,15和18位数字,或者使用前两种类型的内置类型)。

结果和注释首先,代码如下。

$ gnatmake fp_torture.adb
gcc -c fp_torture.adb
gnatbind -x fp_torture.ali
gnatlink fp_torture.ali
$ ./fp_torture
 41 ( 5.96046E-08)
Error representing float  2.14748E+09 as integer
 49 ( 1.11022302462516E-16)
 2147483647 ( 0.00000000000000E+00)
 41 ( 5.42101086242752217E-20)
 2147483647 ( 0.00000000000000000E+00)
$

正如我们所看到的,浮点计算重现了C ++失败点,并确认使用了387 80位浮点数。但是将(非常接近1的数字)转换回整数,比较起作用。

看过这个,在C ++示例中添加适当的舍入确实可以使比较工作。在MAX_INT处添加终止条件,“double n”然后起作用。

++n无法递增n时,“float n”中出现了一个点,因此迭代器会停止迭代,但这是另一回事!

下面的Ada版本创建了一个通用版本,因此我可以使用任何浮点类型对其进行实例化。 (异常处理程序是必要的,因为2 ^ 31 - 1转换为32位浮点数和后溢出...)

with Ada.Text_IO;   
use Ada.Text_IO;

procedure FP_Torture is

    generic
       type Float_Type is digits <>;
    procedure Test_FP;

    procedure Test_FP is
       F : Float_Type;
    begin
       -- for ( n = 2; 1 / n * n == 1; ++ n ) ;
       for i in 2 .. Natural'Last loop
          F := Float_Type(i);
          exit when 1.0 / F * F /= 1.0;
       end loop;
       Put_Line(natural'image(natural(F)) & " (" 
               & Float_Type'image(1.0 - (1.0 / F * F)) & ")");

       -- for ( ; (int) ( 1 / n * n ) == 1; ++ n ) ;
       for i in 1 .. Natural'Last  loop
          F := Float_Type(i);
          exit when natural(1.0 / F * F) /= 1;
       end loop;
       Put_Line(Natural'image(Natural(F)) & " (" 
               & Float_Type'image(1.0 - (1.0 / F * F)) & ")");
    exception
       when Constraint_Error => 
           Put_Line("Error representing float " & Float_Type'image(F) 
                    & " as integer");
    end;

    type Big_Float is digits 18;

    procedure Test7 is new Test_FP(Float);
    procedure Test15 is new Test_FP(Long_Float);
    procedure Test18 is new Test_FP(Big_Float);

begin
    Test7;
    Test15;
    Test18;
end FP_Torture;