2017-11-30 23 views
6

このコードを見てください。 10/3 return 3.333333 604736328125000と私が計算機で3を掛けると、私は9.99になりますが、コードで同じにすると正確に10.00になります。 それはどのように考えられるのですか?なぜ10/3それはCで正確ですか?

0000000000400497 <main>: 
    400497:  55      push rbp 
    400498:  48 89 e5    mov rbp,rsp 
    40049b:  f3 0f 10 05 b1 00 00 movss xmm0,DWORD PTR [rip+0xb1]  # 400554 <_IO_stdin_used+0x4> 
    4004a2:  00 
    4004a3:  f3 0f 11 45 fc   movss DWORD PTR [rbp-0x4],xmm0 
    4004a8:  f3 0f 10 4d fc   movss xmm1,DWORD PTR [rbp-0x4] 
    4004ad:  f3 0f 10 05 a3 00 00 movss xmm0,DWORD PTR [rip+0xa3]  # 400558 <_IO_stdin_used+0x8> 
    4004b4:  00 
    4004b5:  f3 0f 59 c1    mulss xmm0,xmm1 
    4004b9:  f3 0f 11 45 f8   movss DWORD PTR [rbp-0x8],xmm0 
    4004be:  b8 00 00 00 00   mov eax,0x0 
    4004c3:  5d      pop rbp 
    4004c4:  c3      ret  
    4004c5:  66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 
    4004cc:  00 00 00 
    4004cf:  90      nop 

私はmulssは、CPUの機能により丸みを帯びていると思う:

#include <stdlib.h> 
#include <stdio.h> 

int main() { 

    float v = 10.f/3.f; 
    float test = v*3.f; 
    printf("10/3 => %25.25f \n (10/3)*3 => %25.25f\n",v,test); 
    return 0; 
} 

これはデフォルトのgcc 7.2.1にパラメータを使用してコンパイルのprintfのないアセンブリコードは、あります。メモ、10/3について

GNU BCプログラムは3.3333333333333333333333(* 3 => 9.9999)を返しおよびScilabのに3.333333333333333 631(* 3 => 10)を返します。

+0

コメントは議論の対象外です。この会話は[チャットに移動]されています(http://chat.stackoverflow.com/rooms/160240/discussion-on-question-by-amanda-osvaldo-why-10-3-its-exact-in-c) 。 – Andy

+1

'mulss'の結果は、デフォルトのIEEE754丸めモードを使用して丸められます。丸めモードは、最も近い丸めであり、引き分けとしても機能します。 (あなたのプログラムは '#pragma FENV_ACCESS ON'を使用せず、FP丸めモードを何かに設定するので、コンパイラはデフォルトの丸めモードを使用するプログラムを生成します) –

答えて

8

このように表示がうまくいくので、最終的に結果として10になることになります。 floatとdoubleの両方の実装で同じことができます。

のは、二重を使用した例を見てみましょう:

我々は%aを使用した16進数浮動小数点表記で10./3.をプリントアウトした場合、我々はこれを取得:

0x1.aaaaaaaaaaaabp+1 

これは、IEEE754 double表現0x401aaaaaaaaaaaabにアップ一致しました。

正規化の上の数字は次のようになります。バイナリで

0x3.5555555555558 

11.0101010101010101010101010101010101010101010101011 

のではなく、3倍するの3回を追加してみましょう、物事をシンプルに保つために:

 11.0101010101010101010101010101010101010101010101011 
+ 11.0101010101010101010101010101010101010101010101011 
--------------------------------------------------------- 
    110.1010101010101010101010101010101010101010101010111 
+ 11.0101010101010101010101010101010101010101010101011 
--------------------------------------------------------- 
    1010.0000000000000000000000000000000000000000000000000 

どの正確に10です。

EDIT:

私は最後の数桁で数学をボットしたように見えます。実際の合計:

 11.0101010101010101010101010101010101010101010101011 
+ 11.0101010101010101010101010101010101010101010101011 
--------------------------------------------------------- 
    110.1010101010101010101010101010101010101010101010110 
+ 11.0101010101010101010101010101010101010101010101011 
--------------------------------------------------------- 
    1010.0000000000000000000000000000000000000000000000001 

だからそうではありません正確 10が、最下位ビットによってオフ。

私はfloatを使用するときに同様の違いに気付きました。

0x1.aaaaaap+1 

正規化:

バイナリで
0x3.555554 

11.0101010101010101010101 

その後、我々は追加:

 11.0101010101010101010101 
+ 11.0101010101010101010101 
------------------------------ 
    110.1010101010101010101010 
+ 11.0101010101010101010101 
------------------------------ 
    1001.1111111111111111111111 
%aで印刷

再び、最下位ビットだけオフになります。

実際の結果がどのように丸められているかについては、私は言うことができません。

+0

最初に追加しましたか?一番右のビットは1であり、ここで示すように '1 + 1'は' 11'の代わりに '10'になるべきではありませんか?そして、私は追加が右端のどこかに迷子「1」を残すべきだと思う。それはおそらくその後切り捨てられるだろうが... – ilkkachu

+0

@ilkkachu:FP乗算は最後まで丸められないので、追加でそれを再作成しようとすると余分なビットを保持する必要がある。私はこれが普通のバイナリ数学であると考えていますが、指数部/仮数部(小数部から整数を区切るための適切な場所に基点があります)ではありません。私はこれが醜いと思われるが、正しい修正は余分な精度を保つことである。 –

+1

@Peterコード、ええ、私は最後に四捨五入することを意味しました。その結果は、整数部分のより重要なビットを有するので、いくつかのビットは小数部分から四捨五入するべきである – ilkkachu

2

あなたがCに表示し、Scilabのはダブルを使用しているようだのに対し、何をScilabの中で参照することは、あなたがCで単一 -precision浮動小数点値(float)を使用していることは何かとの食い違いの理由 - デフォルトでは、精度値(double)です。

あなたは違いhereを見ることができます(ちょうどあなたの番号からfサフィックスを削除し、floatの代わりにdoubleを置きます)。

関連する問題