21

次のコードがあります。

#include <cstdio>
int main()
{
   if ((1.0 + 0.1) != (1.0 + 0.1))
      printf("not equal\n");
    else
      printf("equal\n");
    return 0;
}

gcc (4.4、4.5、および 4.6) を使用して O3 でコンパイルし、ネイティブ (ubuntu 10.10) で実行すると、「等しい」という期待される結果が出力されます。

ただし、同じコードを上記のようにコンパイルして仮想マシン (ubuntu 10.10、virtualbox イメージ) で実行すると、「等しくない」と出力されます。これは、O3 フラグと O2 フラグが設定されているが、O1 以下では設定されていない場合です。clang (O3 および O2) でコンパイルし、仮想マシンで実行すると、正しい結果が得られます。

double を使用して 1.1 を正しく表現できないことを理解しており、 「すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと」を読んだので、そこを指摘しないでください。これは、GCC が行うある種の最適化のようです。どういうわけか、仮想マシンでは機能しないようです。

何か案は?

注: C++ 標準では、この状況での型の昇格は実装に依存すると述べています。GCC は、不等式テストが適用されたときに真を保持する、より正確な内部表現を使用している可能性があります-余分な精度のために?

UPDATE1:上記のコードを次のように変更すると、正しい結果が得られるようになりました。ある時点で、何らかの理由で GCC が浮動小数点制御ワードをオフにしているようです。

#include <cstdio>
void set_dpfpu() { unsigned int mode = 0x27F; asm ("fldcw %0" : : "m" (*&mode)); 
int main()
{
   set_dpfpu();
   if ((1.0 + 0.1) != (1.0 + 0.1))
      printf("not equal\n");
    else
      printf("equal\n");
    return 0;
}

UPDATE2:コードの const 式の性質について尋ねる人のために、次のように変更しましたが、GCC でコンパイルするとまだ失敗します。-しかし、オプティマイザーが次のものもconst式に変換している可能性があると思います。

#include <cstdio>
void set_dpfpu() { unsigned int mode = 0x27F; asm ("fldcw %0" : : "m" (*&mode)); 
int main()
{
   //set_dpfpu();  uncomment to make it work.
   double d1 = 1.0;
   double d2 = 1.0;  
   if ((d1 + 0.1) != (d2 + 0.1))
      printf("not equal\n");
    else
      printf("equal\n");
    return 0;
}

UPDATE3 解決策: virtualbox をバージョン 4.1.8r75467 にアップグレードすると、問題が解決しました。ただし、1 つの問題が残っています。つまり、clang ビルドが機能した理由です。

4

4 に答える 4

10

更新: この投稿を参照してください浮動小数点計算で過剰な精度を処理する方法? 拡張浮動小数点精度の問題に対処します。x86 の拡張精度を忘れていました。決定論的であるはずのシミュレーションを覚えていますが、Intel CPU と PowerPC CPU では異なる結果が得られました。その原因は、Intel の拡張精度アーキテクチャにありました。

この Web ページでは、Intel CPU を倍精度丸めモードにする方法について説明しています: http://www.network-theory.co.uk/docs/gccintro/gccintro_70.html


virtualbox は、その浮動小数点演算がハードウェアの浮動小数点演算と同一であることを保証しますか? Google で検索しても、そのような保証は見つかりませんでした。また、vituralbox の FP ops が IEEE 754 に準拠しているという約束も見つかりませんでした。

VM は、特定の命令セットまたはアーキテクチャのエミュレートを試み、ほとんどの場合成功するエミュレータです。ただし、それらは単なるエミュレーターであり、独自の実装の癖や設計上の問題の影響を受けます。

まだお持ちでない場合は、forums.virtualbox.org に質問を投稿して、コミュニティの意見をご覧ください。

于 2012-01-18T20:13:15.860 に答える
5

はい、これは本当に奇妙な動作ですが、実際には簡単に説明できます。

x86 浮動小数点レジスタでは、内部的により高い精度が使用されます (たとえば、64 ではなく 80)。これは、計算1.0 + 0.1がレジスタ内でより正確に計算されることを意味します (そして、1.1 はバイナリで正確に表現できないため、余分なビットが使用されます)。結果をメモリに格納する場合にのみ、切り捨てられます。

これが意味することは簡単です。メモリからロードされた値をレジスタで新しく計算された値と比較すると、「等しくない」という結果が返されます。したがって、これは VM とは関係なく、VM がない場合でも、コンパイラが生成するコードに依存するだけであり、そこに見られるように簡単に変動する可能性があります。

驚きの浮動小数点のリストに追加してください..

于 2012-01-18T20:55:06.283 に答える
4

非 VM コードの同じ動作を確認できますが、VM がないため、VM 部分はテストしていません。

ただし、コンパイラ (Clang と GCC の両方) は、コンパイル時に定数式を評価します。以下のアセンブリ出力を参照してください ( を使用gcc -O0 test.cpp -S) :

    .file   "test.cpp"
    .section        .rodata
.LC0:
    .string "equal"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $.LC0, %edi
    call    puts
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
        .section        .note.GNU-stack,"",@progbits

アセンブリについては理解できているように見えますが、「等しい」文字列だけが存在し、「等しくない」文字列がないことは明らかです。そのため、比較は実行時にも行われず、「等しい」と出力されるだけです。

アセンブリを使用して計算と比較をコーディングし、同じ動作をするかどうかを確認します。VM での動作が異なる場合は、VM が計算を行う方法です。

UPDATE 1: (元の質問の「UPDATE 2」に基づく)。以下は、gcc -O0 -S test.cpp出力アセンブリです (64 ビット アーキテクチャの場合)。その中には、movabsq $4607182418800017408, %raxラインが 2 回表示されます。これは 2 つの比較フラグのためのもので、まだ確認していませんが、$4607182418800017408 の値は浮動小数点で 1.1 であると推測しています。これを VM でコンパイルすると興味深いでしょう。同じ結果 (2 つの同様の行) が得られた場合、VM は実行時に何かおかしいことを行っていることになります。それ以外の場合は、VM とコンパイラの組み合わせです。

main:
.LFB1:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movabsq $4607182418800017408, %rax
        movq    %rax, -16(%rbp)
        movabsq $4607182418800017408, %rax
        movq    %rax, -8(%rbp)
        movsd   -16(%rbp), %xmm1
        movsd   .LC1(%rip), %xmm0
        addsd   %xmm1, %xmm0
        movsd   -8(%rbp), %xmm2
        movsd   .LC1(%rip), %xmm1
            addsd   %xmm2, %xmm1
        ucomisd %xmm1, %xmm0
        jp      .L6
        ucomisd %xmm1, %xmm0
        je      .L7
于 2012-01-18T20:38:41.513 に答える
2

別の質問を追加したようです:

注: C++ 標準では、この状況での型の昇格は実装に依存すると述べています。GCC は、不等式テストが適用されたときに真を保持する、より正確な内部表現を使用している可能性があります-余分な精度のために?

その答えはノーです。1.1形式のビット数に関係なく、バイナリ形式で正確に表現できるわけではありません。近づくことはできますが、.1.

それとも、10 進数のまったく新しい内部形式のことですか? いいえ、私はそれを信じたくありません。もしそうなら、それはあまり互換性がありません。

于 2012-01-18T20:26:35.683 に答える