16

在尝试处理浮点算术问题时,我遇到了一些令人困惑的事情。

首先,代码。我将问题的本质提炼到了这个例子中:

#include <iostream>
#include <iomanip>

using namespace std;
typedef union {long long ll; double d;} bindouble;

int main(int argc, char** argv) {
    bindouble y, z, tau, xinum, xiden;
    y.d = 1.0d;
    z.ll = 0x3fc5f8e2f0686eee; // double 0.17165791262311053
    tau.ll = 0x3fab51c5e0bf9ef7; // double 0.053358253178712838
    // xinum = double 0.16249854626123722 (0x3fc4ccc09aeb769a)
    xinum.d = y.d * (z.d - tau.d) - tau.d * (z.d - 1);
    // xiden = double 0.16249854626123725 (0x3fc4ccc09aeb769b)
    xiden.d = z.d * (1 - tau.d);
    cout << hex << xinum.ll << endl << xiden.ll << endl;
}

xinum并且xiden应该具有相同的值(何时y == 1),但由于浮点舍入误差,它们没有。我得到的那部分。

当我通过 GDB 运行这段代码(实际上是我的真实程序)以追踪差异时,问题出现了。如果我使用 GDB 重现代码中所做的评估,它会为 xiden 提供不同的结果:

$ gdb mathtest
GNU gdb (Gentoo 7.5 p1) 7.5
...
This GDB was configured as "x86_64-pc-linux-gnu".
...
(gdb) break 16
Breakpoint 1 at 0x4008ef: file mathtest.cpp, line 16.
(gdb) run
Starting program: /home/diazona/tmp/mathtest 
...
Breakpoint 1, main (argc=1, argv=0x7fffffffd5f8) at mathtest.cpp:16
16          cout << hex << xinum.ll << endl << xiden.ll << endl;
(gdb) print xiden.d
$1 = 0.16249854626123725
(gdb) print z.d * (1 - tau.d)
$2 = 0.16249854626123722

您会注意到,如果我让 GDB 计算z.d * (1 - tau.d),它会给出 0.16249854626123722 (0x3fc4ccc09aeb769a),而在程序中计算相同内容的实际 C++ 代码会给出 0.16249854626123725 (0x3fc4ccc09aeb769b)。所以 GDB 必须对浮点运算使用不同的评估模型。任何人都可以对此有所了解吗?GDB 的评估与我的处理器的评估有何不同?

我确实看过这个相关的问题,询问 GDB 评估sqrt(3)为 0,但这不应该是同一件事,因为这里不涉及函数调用。

4

3 回答 3

5

Could be because the x86 FPU works in registers to 80 bits accuracy, but rounds to 64 bits when the value is stored to memory. GDB will be storing to memory on every step of the (interpreted) computation.

于 2012-11-13T00:03:03.817 回答
4

GDB 的运行时表达式评估系统当然不能保证为您的浮点运算执行与您的编译器为计算相同符号表达式的结果而生成的优化和重新排序的机器代码相同的有效机器代码。实际上,保证不会执行相同的机器代码来计算给定表达式的值z.d * (1 - tau.d),因为这可能被认为是程序的一个子集,在运行时以某种任意的“符号正确”方式对其执行隔离表达式求值.

由于优化(替换、重新排序、子表达式消除等)、指令的选择、选择寄存器分配和浮点环境。如果您的代码片段在临时表达式中包含许多自动变量(就像您的那样),则代码生成具有特别大的自由度,即使优化通过次数为零,并且这种自由度带来了 - 在这种情况下 - 失去精度的机会以一种看起来不一致的方式出现的最不重要的位。

您将无法深入了解 GDB 的运行时评估程序为何执行它所做的任何指令,而无需深入了解 GDB 源代码、构建设置和它自己的编译时生成的代码。

您可以在为您的过程生成的程序集上达到峰值,以了解最终存储到ztau和 [相反] 中的xiden工作方式。导致这些存储的浮点运算的数据流可能不像看起来那样。

更容易的是,尝试通过禁用所有编译器优化(例如,-O0在 GCC 上)并重写浮点表达式以不使用临时/自动变量来使代码生成更具确定性。然后在 GDB 中的每一行打断并进行比较。

我希望我能准确地告诉你为什么尾数的最不重要的位被翻转,但事实是,处理器甚至不“知道”为什么有些东西携带了位,而其他的东西却没有,例如,订单没有完整的代码和 GDB 本身的指令和数据跟踪的评估。

于 2012-11-13T00:55:53.983 回答
2

它不是 GDB 与处理器,而是内存与处理器。x64 处理器存储的精度比内存实际保存的精度高(80 位对 64 位)。只要它留在 CPU 和寄存器中,它就会保持 80 位左右的精度,但是当它被发送到内存时,将决定它何时以及如何四舍五入。如果 GDB 将所有间歇性计算结果从 CPU 发送出去(我不知道是这种情况,还是接近),它会在一步都进行舍入,这会导致结果略有不同。

于 2012-11-12T23:59:44.147 回答