为什么 float 不能正确表示 pow(2., 60.) ?

Li.*_*ang 1 floating-point visual-c++

浮点数的计算机表示中我学到了计算机的浮点表示。
\n根据教程,对于32位浮点数,可以存储的最小正归一化数\n是2^(-126)\xef\xbc\x8c,最大归一化数是(2-2^(-23)) *2^(127)\xe2\x89\x88 2^(128)。然而,精度受到 23 位有效数的限制。

\n\n

在我看来,32位浮点数可以表示2^60而不会出现任何错误,因为:

\n\n
    \n
  1. 标志:1
  2. \n
  3. 指数:10111011(十进制187即60+127)
  4. \n
  5. 有效数:0000 ... 0000(23 个零)
  6. \n
\n\n

用指数和尾数的隐藏位(1)来表示2^60就完全足够了。

\n\n

我的测试代码如下(VS2013 + win10):

\n\n
#include <iostream>\n#include <math.h>\n#include <bitset>\n\nusing namespace std;\n\nint main()\n{\n    union\n    {\n        float input;   // assumes sizeof(float) == sizeof(int)\n        int   output;\n    }    data;\n\n    data.input = pow(2., 60.);\n\n    std::bitset<sizeof(float) * CHAR_BIT>   bits(data.output);\n\n\n    std::cout << "Total: " << bits << std::endl;\n\n\n    cout << "Sign: " << bits[31] << endl << "Exponent: ";\n\n    for (int i = 30; i > 22; i--)\n    {\n        cout << bits[i];\n    }\n    cout << endl << "Significand: ";\n\n    for (int i = 22; i >= 0; i--)\n    {\n        cout << bits[i];\n    }\n    cout << endl;\n\n\n    cout.precision(20);\n    cout << data.input << endl;\n    printf("%f", data.input);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我得到输出:

\n\n
    Total: 01011101100000000000000000000000\n    Sign: 0\n    Exponent: 10111011\n    Significand: 00000000000000000000000\n    1152921504606847000\n    1152921504606847000.000000\n
Run Code Online (Sandbox Code Playgroud)\n\n

我打印了二进制表示形式,它是正确的。但我很困惑为什么最后三位数字为零。正确的输出应该是 1152921504606846976。

\n\n

此外,我将代码更改如下:

\n\n
#include <iostream>\n#include <math.h>\n#include <bitset>\n\nusing namespace std;\n\nint main()\n{\n    for (int i = 1; i < 65; i++)\n    {\n        union\n        {\n            float input;   // assumes sizeof(float) == sizeof(int)\n            int   output;\n        }    data;\n\n        data.input = pow(2, i);\n\n        std::bitset<sizeof(float) * CHAR_BIT>   bits(data.output);\n\n\n        cout.precision(20);\n        cout << i << ": " << data.input << endl;\n        //printf("%f\\n", data.input);\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出是:

\n\n
1: 2\n2 : 4\n3 : 8\n    ......\n55 : 36028797018963968\n56 : 72057594037927936\n57 : 144115188075855870\n58 : 288230376151711740\n59 : 576460752303423490\n60 : 1152921504606847000\n61 : 2305843009213694000\n62 : 4611686018427387900\n63 : 9223372036854775800\n64 : 18446744073709552000\n
Run Code Online (Sandbox Code Playgroud)\n\n

零从 2^57 开始出现。谁能告诉我为什么会发生这种情况?

\n

Eri*_*hil 5

这并不是无法正确float表示 2 60。是 Microsoft\xe2\x80\x99s 软件无法正确将 2 60转换为十进制(也就是说,故障出在格式化代码中,而不是算术中float,尽管 Microsoft\xe2\x80\x99spow之前的实现也是不准确的) )。无论涉及的实际值是多少,您使用的软件仅生成 17 位十进制数字。使用 Apple LLVM 10.0.0 (clang-1000.11.45.5) 编译的同一程序会生成:

\n\n
\n55: 36028797018963968\n56: 72057594037927936\n57: 144115188075855872\n58: 288230376151711744\n59: 576460752303423488\n60: 1152921504606846976\n61: 2305843009213693952\n62: 4611686018427387904\n63: 9223372036854775808\n64: 18446744073709551616\n
\n\n

Microsoft\xe2\x80\x99s 的行为是 C 标准所允许的,但当然在数学上并不好。

\n