【问题标题】:What is the least positive integer with no reciprocal in typical floating point?典型浮点数中没有倒数的最小正整数是多少?
【发布时间】:2012-11-26 06:39:48
【问题描述】:

一个常见的假设是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";
    }
}

ideone.com 上使用 GCC 4.3.4 结果是

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 硬件。是否有适合测试的可靠值?

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

【问题讨论】:

  • 它可能早在1/3 * 3 就失败,因为1/3 不能用二进制浮点数精确表示。事实证明准确的唯一方法是,如果 1/3 * 3 恰好朝 1 舍入,而不是 0.99999...1.00000001 之类的。
  • @Mysticial 可以,但通常不会。似乎 FPU 的设计目的是不这样做。我想知道可靠失败的最低值是多少。或者,FPU 采用什么二进制技巧能够将数字正确四舍五入到 40,但仍然在该范围内的不同点处失败。
  • 我很想投票结束这个问题,因为它没有说明浮点错误或推理浮点运算的好方法。具体询问 1/x*x==1 失败的条件或存在 x 的 r 使得 x*r==1 评估为 true 提供的关于浮点如何工作的见解很少,并且为预测或控制任何其他情况下的错误提供很少的基础。此外,该问题忽略了语言标准或编译器,但尝试使用语言和编译器来实验性地调查问题。
  • 对浮点属性的认识非常低;像这样的问题可能会帮助程序员更多地注意真正的陷阱,这就是我投赞成票的原因。
  • @BrianDrummond:如果您了解了这个问题的答案,那么这对任何其他浮点问题有什么帮助?我们得到的答案并没有解释除法中的舍入和随后的乘法如何结合产生 1 或不产生 1。它们没有详细说明浮点格式或如何计算误差范围。关于哪个 x 是具有此错误的最小整数没有什么意义。这只是一个随机的问题,与其他任何事情几乎没有关系。这不是设计浮点计算的人使用浮点的方式。

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


【解决方案1】:

双精度:

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.ffffffffffff8 = 0x1.ffffffffffff0p-1

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

更一般地,如果 f 是 [1, 2) 中的浮点数,则 f 有倒数。在通常的四舍五入算术中,如果一个数字位于 [1 - 2-54, 1 + 2-53] 中,则该数字将四舍五入为 1。 请注意,最接近 1/f 的双精度数,例如 d,距离 1/f 小于 2-54。如果 d > 1/f,那么我们就是黄金; 1 -54) -54 * f -53,因此 f*d 舍入为 1。如果 d -53。如果是,则 f*d 位于 [1 - 2-53, 1 - 2-54)。如果你取 e = 2-53 + d,那么 e*f > 1 并且 e*f = d*f + 2-53*f -53 + 2-52 = 1 + 2-53,再次四舍五入为 1。

编辑:上面的推理是错误的,因为两个连续双打之间的步幅相差了两倍。没有倒数的双精度示例是 0x1.ffffffbffffffe。 0x1.0000002000001p-1 太小,但 0x1.0000002000002p-1 太大。没有倒数的整数的最小示例是 237。1/237 大约是 0x1.1485f0e0acd3B68c6Bp-8,四舍五入为 0x1.1485f0e0acd58p-8。这个数字太小了,而它之后的下一个 double 太大了。

【讨论】:

  • 有趣。 [1,2) 中倒数的存在是否意味着所有归一化数都有精确的倒数? (我想这是有道理的。)那么这一切意味着什么?与可表示值不同的精确结果是否存在模式?
  • 并非所有普通浮点数都存在倒数。但这仅在 1p1023 和 1.fffffffffffffp1023 之间失败,因为倒数可能不正常。我不太明白你的第二个问题。
  • 好的。我的意思是,我尝试的 FPU + 编译器在 41、45、49 时失败,而不是更高或更低的数字是随机的吗?或者d &lt; 1/fd 是否随着f 的增长或f 的特定范围向下舍入变得更有可能?
  • 浮点数与随机概率无关。我认为数论巧合就是这里发生的事情。也就是说,要使 1/k 不能成为 k 的倒数,需要将 1/k 向下舍入,1/k 非常接近两个浮点数之间的中间值,并且当您相乘时,舍入实际上会产生影响一起。我还没有弄清楚 41 和 45 是怎么回事(抱歉;我不太在意)。
  • 只是为了好玩:在通常的假设下(IEEE 754 binary64 格式,round-ties-to-even),并假设1 &lt;= n &lt;= 2**53n 上的条件 1.0 / n * n != 1.0 正好2**(e - 2) &lt; 2**(e + 52) % n &lt; n / 2,其中% 表示通常的余数运算,e 是唯一整数,使得2**(e-1) &lt;= n &lt; 2**e
【解决方案2】:

这个问题似乎与 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;

【讨论】:

    猜你喜欢
    • 2021-11-06
    • 2011-03-29
    • 1970-01-01
    • 2017-02-11
    • 2017-02-08
    • 1970-01-01
    • 1970-01-01
    • 2019-06-16
    • 2019-10-30
    相关资源
    最近更新 更多