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;
}

xinumxidenは同じ値(の場合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が0に評価されることについて尋ねるこの関連する質問を見ましたsqrt(3)が、ここには関数呼び出しが含まれていないため、これは同じことではありません。

4

3 に答える 3

5

x86 FPU はレジスタで 80 ビットの精度で動作しますが、値がメモリに格納されると 64 ビットに丸められるためです。GDB は、(解釈された) 計算のすべてのステップでメモリに格納されます。

于 2012-11-13T00:03:03.817 に答える
4

GDB のランタイム式評価システムは、同じシンボリック式の結果を計算するためにコンパイラーによって生成され、最適化および並べ替えられたマシン コードと同じ効果的なマシン コードを浮動小数点演算に対して実行することは保証されていません。実際、与えられた expression の値を計算するために同じマシンコードを実行しないことが保証されていz.d * (1 - tau.d)ます。これは、実行時に任意の「記号的に正しい」方法で分離された式の評価が実行されるプログラムのサブセットと見なされる可能性があるためです。 .

浮動小数点コードの生成と CPU によるその出力の実現は、最適化 (置換、並べ替え、部分式の削除など)、命令の選択、選択により、他の実装 (ランタイム式エバリュエーターなど) との記号的な不一致が特に発生しやすくなります。レジスタの割り当てと浮動小数点環境。あなたのスニペットが一時的な式に多くの自動変数を含んでいる場合 (あなたのもののように)、コード生成には特に大きな自由があり、最適化パスがゼロでもあります。矛盾しているように見える方法で最下位ビット。

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 に答える