下のCプログラムをLinuxのgccでコンパイルした場合とgcc -Oでコンパイルした場合とで結果が異なるのですが、なぜなのでしょう?昔モータ制御プログラムを作っていたころ書き留めたメモを偶然見つけて、文脈は忘れてしまったのですが、依然理由がわからないので質問してみました。


#include <stdio.h>
main()
{
double x,y;
int n1, n2;
x=10.0;
y=204.7*x;
n1=(int)y;
n2=(int)(204.7*x);
printf("%d %d\n", n1, n2);
}

回答の条件
  • 1人3回まで
  • 登録:2006/05/10 17:48:23
  • 終了:2006/05/17 17:50:04

回答(4件)

id:kilia No.1

kilia回答回数1ベストアンサー獲得回数02006/05/10 17:59:47

ポイント23pt

http://www.yahoo.co.jp

  • gcc version 3.4.5 20051201 (Red Hat 3.4.5-2)
  • gcc version 2.95.2 19991024 (Solaris)

gccのオプションを-O, -O3, 最適化なしの3通りで、

上記2つの環境でやってみましたが、どちらも2047 2047となります。

gccのバージョンを教えていただけますか?

id:tkysg

ありがとうございます。

gcc version 3.3.1 (Turbo Linux 2.6.0-24) です。

gcc -O だと 2047 2047 ですが、gcc だと 2047 2046 と出ます。(ccでも同じ)

大型機のSunOS 5.9 gcc version 2.7.0 だとこの現象は出ませんでしたので不思議に思っています。よろしくお願いします。

2006/05/10 18:11:22
id:aki73ix No.2

aki73ix回答回数5224ベストアンサー獲得回数272006/05/10 18:57:23

ポイント23pt

コンパイラが吐き出したソースを見れば納得行くと思います(-S オプションでコンパイルすると ~.sというアセンブラソースファイルが出力されます)

まず gcc test.c

結果は 2047 2046

    .file    "test.c"

    .section    .rodata

.LC1:

    .string    "%d %d¥n"

    .align 8

.LC0:

    .long    1717986918

    .long    1080661606

    .text

.globl main

    .type    main,@function

main:

    pushl    %ebp

    movl    %esp, %ebp

    subl    $40, %esp

    andl    $-16, %esp

    movl    $0, %eax

    subl    %eax, %esp

    movl    $0, -8(%ebp)

    movl    $1076101120, -4(%ebp)

    fldl    -8(%ebp)

    fldl    .LC0

    fmulp    %st, %st(1)

    fstpl    -16(%ebp)

    fldl    -16(%ebp)

    fnstcw    -26(%ebp)

    movw    -26(%ebp), %ax

    movb    $12, %ah

    movw    %ax, -28(%ebp)

    fldcw    -28(%ebp)

    fistpl    -20(%ebp)

    fldcw    -26(%ebp)

    fldl    -8(%ebp)

    fldl    .LC0

    fmulp    %st, %st(1)

    fldcw    -28(%ebp)

    fistpl    -24(%ebp)

    fldcw    -26(%ebp)

    subl    $4, %esp

    pushl    -24(%ebp)

    pushl    -20(%ebp)

    pushl    $.LC1

    call    printf

    addl    $16, %esp

    leave

    ret

.Lfe1:

    .size    main,.Lfe1-main

    .ident    "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"

これは、double型で実数計算を行っているのがわかります、要するに丸め誤差で一方が 2046になったわけです

次に gcc test.c -O1

結果は 2047 2047

    .file    "test.c"

    .section    .rodata.str1.1,"aMS",@progbits,1

.LC2:

    .string    "%d %d¥n"

    .text

.globl main

    .type    main,@function

main:

    pushl    %ebp

    movl    %esp, %ebp

    subl    $8, %esp

    andl    $-16, %esp

    subl    $4, %esp

    pushl    $2047

    pushl    $2047

    pushl    $.LC2

    call    printf

    leave

    ret

.Lfe1:

    .size    main,.Lfe1-main

    .ident    "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"

このようにプリプロセッサが204.7*10を計算してしまい2047にしてしまっているので計算することなく 2047が出力されていることがわかります

これでは 2047が出てくるのは当然・・・というわけです

※:-O1にしたのは -Oより最適化が少なく比較がしやすいからです

http://www.linux.or.jp/JM/html/GNU_gcc/man1/gcc.1.html

id:tkysg

ありがとうございます。-Sオプション知りませんでした。

それにしても最適化ってブラックボックスですね

(いろんなAI技術が入ってそう)

勉強になりました。

ところで、204.7*10.0 が 2046 に丸められるのってなんだか怖いのですが、こんなもんなのでしょうか?(普通のpentium4パソコンです)あるいはCのプログラムの書き方がなってないのでしょうか?

2006/05/11 03:55:50
id:aki73ix No.3

aki73ix回答回数5224ベストアンサー獲得回数272006/05/11 11:53:04

ポイント22pt

> ところで、204.7*10.0 が 2046 に丸められるのって

> なんだか怖いのですが、こんなもんなのでしょうか?

キャスト宣言による実際の処理の違いです

実数間の演算であれば

fld 204.7

fmul 10.0

fstp st0

が実行され 204.7*10.0は2047で計算され 2047が格納されます

ところが、(int)(204.7*10.0)

を行うと

fld 204.7

fmul 10.0

_ftol (fistp)

となります

これはfmulの直後の204.7の内部表現の値に10をかけた浮動小数点レジスタの内部表現の計算途中の桁落ちした数値を ftolでlong型に変換していることを意味します

つまり浮動小数点プロセッサの出した値が直接反映されず、正しい値にならなくなるわけです


もう少し詳しく見てみましょう

.long 1717986918

.long 1080661606

これは 0x4069966666666666 で204.7のことです


fld 204.7

fmul 10.0

の演算結果は

0x4009ffdffffffffffc00 です(FPU内では10バイトで計算される)

ここで、stp命令がくれば10バイトの演算結果が8バイトに補正され

0x4009ffe000000000 (2047)になります

ところが、 ftol(fistp) が実行されると

0x4009ffdffffffffffc00 を直接INTに変換され、端数が切り捨てられて

0x4009ffd000000000 (2046)になるわけです

参考までに long double (テンポラリリアル)を使用すると、浮動小数点演算の内部表現のまま直接演算されるので、最適化オプションをつけないと、どちらも丸められて 2046になってしまいます

id:tkysg

どうもありがとうございます。(遅くなりました)

キャストはアーキテクチャの知識を持って使わないと思わぬ結果になるようですね

gcc -Sで吐き出されるアセンブラの解説が載っているWEBページをご存じでしたら教えてください。

2006/05/12 12:30:42
id:aki73ix No.4

aki73ix回答回数5224ベストアンサー獲得回数272006/05/15 10:12:12

ポイント22pt

> キャストはアーキテクチャの知識を持って使わないと思わぬ結果になるようですね

厳密な計算をしたい場合は、インラインアセンブラで

書いた方が、精度の高い場合もあるわけです

今回は明示されたキャストによる不具合ですが、

計算途中に型変換があると、コンパイルオプションや

コンパイラバージョン、MPUなどによって計算に違いが

出ることがあります

> gcc -Sで吐き出されるアセンブラの解説が載っているWEBページをご存じでしたら教えてください。

http://homepage2.nifty.com/kaz1-iwata/index.htm

このページは色々な数値演算について解説があるので

いいと思います

・・・・が、gcc -Sの形式はアーキテクチャによって

違うのでアセンブラのFPU命令を理解した上で、自分で

辿っていった方が確実ですよ

デバッガでFPUレジスタを監視しながら、ステップ実行

するとよいと思います

3回の回答制限に引っかかるので最後のお返事に

なります|。・・)ノ

id:tkysg

このたびは有益な情報をありがとうございました。

大変参考になりました。

2006/05/15 14:39:19

コメントはまだありません

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

絞り込み :
はてなココの「ともだち」を表示します。
回答リクエストを送信したユーザーはいません