• 締切済み

VC6における浮動少数点数値について

はじめて質問させて頂きます。 C言語の基礎演習レベルの題材ですが 想定外の結果が表示され解決できておりません。 内容としては2つのfloat型変数(以下ソースのb,c)に 全く同じ演算処理結果を代入し コンソール表示させるというプログラムです。 #include <stdio.h> void main() { float a = 0, k = 0; float b = 0; float c = 0; a = 0.2; k = 20.0; k += a; b = k - 20.0; c = k - 20.0; printf( "%f %f %f %f\n", a, b, c, k ); if( b == c ) { printf( "*\n" ); } while( getchar() != EOF ){} } 予想実行結果: 0.200000 0.200000 0.200000 20.200000 * 実際の結果: 0.200000 0.200000 0.200001 20.200001 当方、浮動少数点数値の扱いにも不慣れな為、原因不明です。 なぜこのような結果になるのか、どなたかご教授頂けないでしょうか。 宜しくお願い致します。

みんなの回答

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.10

ついでにいうと「20.0が2進化浮動小数点数の循環小数だった事」は意味不明>#7. 「2進化浮動小数点数の循環小数」ってなんだろう? まあ, いずれにしてもこの辺の細か~い誤差はあったりするので浮動小数同士を == や != で比較するのは割と危険.

全文を見る
すると、全ての回答が全文表示されます。
  • nda23
  • ベストアンサー率54% (777/1415)
回答No.9

そもそも、浮動小数点型を10進数にした時の信頼精度をご存知ですか? 単精度では6桁、倍精度では15桁です。これは少数部だけでなく、整数 でも発生する問題です。入口/出口だけではなく、途中の一時的な値 についても、同桁数以内になっていないと、誤差が拡大します。 それらの事情を考慮したうえで、ワークも含め型を決定してください。 演算誤差を抑えるため、精度の高い変数に代入してから演算する等の 方法を明示的に採用する方が分かり易いと思いますし、周囲の納得を 得られるのではないでしょうか。 尚、最適化を行ったため、不具合が生じる例は実数演算以外にも幾つか 見受けられます。

全文を見る
すると、全ての回答が全文表示されます。
  • titokani
  • ベストアンサー率19% (341/1726)
回答No.8

>簡単に言えば、上記のプログラムは >b = (float)((double)20.2 - (double)20.0); >c = (float)((double)(float)20.2 - (double)20.0); >というプログラムと等価。 いやいや、いかに最適化といえど、勝手に型を変えてはいけません。 k+=aの型は、あくまでもfloatです。これは規格できっちり定められています。 今回は最適化によって、演算誤差が異なってしまうことの一例とだと思います。

全文を見る
すると、全ての回答が全文表示されます。
  • chie65535
  • ベストアンサー率43% (8536/19408)
回答No.7

ちょっと訂正と追加。 >簡単に言えば、上記のプログラムは >b = (float)((double)20.2 - (double)0.2); >c = (float)((double)(float)20.2 - (double)0.2); >というプログラムと等価。 は 簡単に言えば、上記のプログラムは b = (float)((double)20.2 - (double)20.0); c = (float)((double)(float)20.2 - (double)20.0); というプログラムと等価。 の間違い。 不運だったのは「20.0が2進化浮動小数点数の循環小数だった事」なのかも知れない。

全文を見る
すると、全ての回答が全文表示されます。
  • chie65535
  • ベストアンサー率43% (8536/19408)
回答No.6

最適化により k += a; b = k - 20.0; c = k - 20.0; は b = (k += a) - 20.0; c = k - 20.0; となっています。 そして、暗黙的な型キャストにより、以下のようになります。 //部分式「(k += a) - 20.0」はすべてdouble型で計算されている //floatへの変換により精度が落ちるのは、bへの代入とkへの代入のみ //(k += a)の値そのものはdoubleの精度を保持している事に注意 b = (float)((k += a) - 20.0); //kを取り出すと、floatからdoubleへの変換が起きる //従って、この時点での部分式「(k)」は「(double)20.2」と等しくない c = (double)(float)(k) - 20.0; 簡単に言えば、上記のプログラムは b = (float)((double)20.2 - (double)0.2); c = (float)((double)(float)20.2 - (double)0.2); というプログラムと等価。 「誰がどう見ても、この2つの式は異なる」ので「結果が異なる」のは当たり前。 なお「こういう現象が起きた原因」は「最適化」にあり、言語仕様上「最適化は環境に依存し、コンパイラごとに異なる」ので、VC6のバグとは言えない。 コンパイル時に最適化を抑止しなかった事、最適化による実行結果の差異を意識しない書き方をした事を考えれば、こう書いた人間に責任があると言える。 双方で同じ結果を得たいのであれば k += a; c = 0.0;//上のkへの代入と下のkの参照をぶった切って最適化させない b = k - 20.0; c = k - 20.0; と書くか k += a; b = (double)(float)(k) - 20.0;//明示的にfloatに精度を落とさせる c = (double)(float)(k) - 20.0;//明示的にfloatに精度を落とさせる と書くか k += a;//これも最適化されて意味無くなるかも知れないが b = (0.0,k) - 20.0;//カンマ演算子で「捨て演算」をして最適化させない c = k - 20.0; と書きましょう。

全文を見る
すると、全ての回答が全文表示されます。
現在サポートで確認中のため、回答者本人のみ投稿内容をご覧いただけます。ご迷惑おかけいたしますが、しばらくお待ちください。
  • titokani
  • ベストアンサー率19% (341/1726)
回答No.4

VC++2005で試したところ、 debug 0.200000 0.200001 0.200001 20.200001 * release 0.200000 0.200001 0.200001 20.200001 * VC6だと、 debug 0.200000 0.200000 0.200001 20.200001 release 0.200000 0.200000 0.200000 20.200000 * でした。 VC6debugのアセンブリコードは、 8: float a = 0, k = 0; 00401028 mov dword ptr [ebp-4],0 0040102F mov dword ptr [ebp-8],0 9: float b = 0; 00401036 mov dword ptr [ebp-0Ch],0 10: float c = 0; 0040103D mov dword ptr [ebp-10h],0 11: 12: a = 0.2; 00401044 mov dword ptr [ebp-4],3E4CCCCDh 13: k = 20.0; 0040104B mov dword ptr [ebp-8],41A00000h 14: 15: k += a; 00401052 fld dword ptr [ebp-8] 00401055 fadd dword ptr [ebp-4] 00401058 fst dword ptr [ebp-8] 16: b = k - 20.0; 0040105B fsub qword ptr [__real@8@4003a000000000000000 (00426030)] 00401061 fstp dword ptr [ebp-0Ch] 17: c = k - 20.0; 00401064 fld dword ptr [ebp-8] 00401067 fsub qword ptr [__real@8@4003a000000000000000 (00426030)] 0040106D fstp dword ptr [ebp-10h] 18: 19: printf( "%f %f %f %f\n", a, b, c, k ); でした。 まさしく、#2さんのおっしゃる通りですが、VC6のバグといっていいかもしれませんね。

mo109n
質問者

お礼

回答ありがとうございます。 #2さんへのお礼で記載させて頂いたソースの修正行った結果からも 納得のいく回答を頂きました。 ありがとうございます。

全文を見る
すると、全ての回答が全文表示されます。
  • nda23
  • ベストアンサー率54% (777/1415)
回答No.3

単精度実数では符号1、指数8、仮数24ビットで表現します。 コンピュータの中身は2進法なので、0.2の表現が難しい。 先ず、2の-3乗である0.125が入り、2の-4乗の0.0625が入り、・・・ という具合です。2進で示すと以下の通りです。 0-01111100-(1)10011001100110011001101 左端は符号で、正なので0です。次の8ビットは0x7Cですが、0x7Fが 0なので、0x7Cは-3を示します。(1)は実データ上は存在しませんが、 有効ビットの最上位を仮定するので、常に1があるものとして操作 します。その右は2の-4乗となりますね。ここに20を足すと、最上位は 2の4乗になるので、指数は0x83(指数の0点は0x7F)になります。 0-10000011-(1)01000011001100110011010 →1001101が失われる。 整数部を優先するため、最初の状態から右の7ビットが消えます。 失われたビットの最上位が1なので、切り上げられ、最後が1001では なく1010になります。ここから20を引くと、上位5ビットが無くなり、 有効ビットが最上位になるようシフトされます。 0-10000011-(0)00000011001100110011010 ←上位5ビットが消える。 0-01111100-(1)10011001100110100000000 ←左シフトする。(0補充) 0-01111100-(1)10011001100110011001101 ←最初の0.2の時の内容                   ↑ 2の-18乗のところが切り上がっているのが分かりますか? これが10進に変換した時の「誤差」になるのです。 因みに2の-1乗は0.5なので、右にシフトしてこぼれたビットが1なら 切り上げます。つまり、四捨五入というわけです。 ご理解いただけましたか?

全文を見る
すると、全ての回答が全文表示されます。
  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.2

そこではなく「同じ計算をしているはずの b と c でなぜ値が異なるのか」を問題にしているのだと思います>#1. 「ひょっとして」ですが, ひょっとすると「b を計算するときには直前の計算で求めた k の値をそのまま使い, c を計算するときには k の値をメモリから読みだしている」可能性があります. つまり, b を計算するときには直前の計算で求めた (double としての) k の値を使い, c を計算するときにはメモリに格納した (float としての) k の値を使っているという可能性です. 手元の VS2008 でこの状況をシミュレートすると, 確かに言われるような結果になります. また, printf のフォーマットをすべて %.16f にすると 0.2000000029802322 0.2000000029802322 0.2000007629394531 20.2000007629394530 という結果が得られています.

mo109n
質問者

お礼

回答ありがとうございます。 titokaniさんも同意見のようです。 実際にソースに変更加えてみたらよく理解できました。 以下のような変更加えたらbとcは同じ結果が得られました。 大変参考になります。 k += a; c = 0; ← 追加 b = k - 20.0; c = k - 20.0; 結果: 0.200000 0.200001 0.200001 20.200001

全文を見る
すると、全ての回答が全文表示されます。
回答No.1

興味深い内容ですね。 VC6が手元にあるので簡単に確認してみました。 以下に要素のある個所からのメモリダンプ(一部)を付記します >a = 0.2;  a = cd,cc,4c,3e, >k = 20.0;  k = 00,00,a0,41, >k += a;  k = 9a,99,a1,41, >b = k - 20.0;  b = 00,cc,4c,3e, a(0.2)とk(20.00)では指数部(4Byte目)の内容が違っています。 その為、計算前に同じ指数の条件変更した際に発生する誤差のようですね。

mo109n
質問者

お礼

回答ありがとうございます。 丸め誤差のようなものだと理解させて頂きました。 大変参考になります。

全文を見る
すると、全ての回答が全文表示されます。

関連するQ&A

  • 浮動小数点表現

    浮動小数点表現の問題で以下の実行結果を元にfloat型変数 f に与えた実数の実際に格納されているビット列を表示するプログラムを作りたいのですが、 #include<stdio.h> main() { float a; scanf("%f",&a); printf("%f",a); } この程度までしか作れません。ポインタを使ってアドレスを表示することは分るのですが…。 どなたか教えていただけると助かります。 実行結果 Size of Float : 4 byte Size of Int : 4 byte f=0.500000000000000 00111111000000000000000000000000

  • scanf関数のプログラムをgetchar関数で

    scanf関数を使って四則演算、論理輪、論理積をint、float、double型で表示するプログラムを作ったのですが これをscanf関数ではなく、getchar関数で組みなおし、関数化する課題が出ました。 そのままscanf関数のところだけを変えても型が違うというエラーが出てうまくいきません。 どうすればいいでしょうか? 一応scanf関数で組んだプログラムの一部を載せておきます。 #include <stdio.h> #include <math.h> main() { float a , b; float x[5]; printf("正の数字を2つ入力して下さい(小数点を含めて4ケタまで):\n"); for(;;) { printf("\na="); scanf("%f" , &a); if(a>=0 && a<=9999 && a) { break; } else { printf("****aに入力エラー****\n"); printf("数字は4ケタ以内の正数を入力:\n"); continue; } } for(;;) { printf("b="); scanf("%f" , &b); if(b>=0 && b<=9999 && b) { break; } else { printf("****bに入力エラー****\n"); printf("数字は4ケタ以内の正数を入力:\n"); continue; } } x[0] = a+b; x[1] = a-b; x[2] = a*b; x[3] = a/b; x[4] = a||b; x[5] = a&&b; printf("\n"); printf("int型 結果:\n足し算=%d\n" , (int)x[0]); printf("引き算=%d\n" , (int)x[1]); printf("掛け算=%d\n" , (int)x[2]); printf("割り算=%d\n" , (int)x[3]); printf("論理和=%d\n" , (int)x[4]); printf("論理積=%d\n" , (int)x[5]); printf("\n"); printf("float型 結果:\n足し算=%f\n" , x[0]); printf("引き算=%f\n" , x[1]); printf("掛け算=%f\n" , x[2]); printf("割り算=%f\n" , x[3]); printf("論理和=%f\n" , x[4]); printf("論理積=%f\n" , x[5]); ・ ・ ・ getchar(); }

  • C言語の実数型の足し算

    C言語初心者です。関数の勉強していて、実数型計算に出くわしました。 #include <stdio.h> float add(float a, float b) { return a+b; } int main(void) { float x=10.5,y=20.3; printf("%f %f\n",x,y); printf("%f\n",add(x,y)); return 0; } としたら、 10.500000 20.299999 30.799999 という結果になりました。今のところint型でずーっと勉強していたので、20.3の20.299999表記が怪しく感じられ、結果も同様に怪しく感じられます。どうして、10.5+20.3=30.8とすっっきり表示してくれないのでしょうか。

  • 浮動小数点の誤差のあわせ方

    文字列からdouble型変換で他のPGと誤差がでてしまうのですが、 なんとか同じBinaryにしたいので教えてください。 1.489を他のPGのHEXで表した結果では、 printf("1.489 = %08lx%08lx\n", atof((double)???)); >3ff7d2f1a9fbe76c 私の作ったPGでは printf("1.489 = %08lx%08lx\n", atof("1.489")); >3ff7d2f1a9fbe76d の結果になります。 丸めれば、同じになるような気がしますが、 その方法がわかりません。 SPARC でCのライブラリかなにかあるでしょうか? どうかよろしくおねがいします。

  • 浮動小数点エラー

    #include<stdio.h> main (){ float D[5][6] = { {0,0,151.9058382,111.8925551,238.1626765,145.6362657},   {0,151.9058382,0,256.9532465,317.3522119,232.5861795}, {0,111.8925551,256.9532465,0,181.0294098,114.6471}, {0,238.1626765,317.3522119,181.0294098,0,93.41311845} ,   {0,145.6362657,232.5861795,114.6471,93.41311845,0}, };   float data[100][100] ; int n=5; float sum; int i,l; for(i=1;i<=n-2;i++) { sum=0; for(l=i+2;l<=n;l++) { sum=sum+D[i][l]; } ◎ data[i]=D[i][i+1]/(sum/(n-(D+1)));  ←この行 } for (i=1;i<=n;i++){ printf("%f",data[i]); } return (0); } 上のプログラムで、◎の行に浮動小数点の不正な使用とエラーがでてしまいます。 参考書、ヘルプで調べたもののなぜエラーとなるか分かりませんでした。 解決できますでしょうか?

  • C言語 プログラミングで行詰まりました…

    標準入力(キーボード)からi,jk,nの値を入力し、次の漸化式を計算し、X_0からX_nまで求めるプログラムを作成したいのですが、うまく表示されません。どかがおかしいのかご指摘お願いします。 <漸化式> X_n=(a+b)/X_(n-1) , X_0=c(n=0) ================================================================== #include<stdio.h> float f_X(int a,int b,float c) { float y; y=(a+b)/c; return y; } int main (void) { int number,i,j; float k,l,n,X; printf("i:"); scanf("%d", &i); printf("j:"); scanf("%d", &j); printf("k:"); scanf("%f", &k); printf("n:"); scanf("%f", &n); X=k; printf("X_0= %.6f\n",X); for(number=1;number<=n;number++) { l=f_X(i,j,X); printf("X_%d= %.6f \n",number,l); X=l; } return 0; } ===================================================================

  • getcharの連続について

    6つの塩基(A、T、G、C)を入力して、それを一列に並べるというものなのですが、うまくいかないので、質問させていただきます。 #include<stdio.h> main() { char b1,b2,b3,b4,b5,b6; printf("6つの塩基を入力してください。\n"); printf("1つ目の塩基は?\n"); b1=getchar(); printf("2つ目の塩基は?\n"); b2=getchar(); printf("3つ目の塩基は?\n"); b3=getchar(); printf("4つ目の塩基は?\n"); b4=getchar(); printf("5つ目の塩基は?\n"); b5=getchar(); printf("6つ目の塩基は?\n"); b6=getchar(); printf("配列は%c%c%c%c%c%cです。\n",b1,b2,b3,b4,b5,b6); } こうすると実行したときに b1= b2=b3= b4=b5=となってうまく実行できないんですが、どうしたらよいのでしょうか? ちなみに「getcharと変数とprintfとscanfを使う」と問題の条件に書いてあるので、これだけで作れという問題ですが・・・

  • 浮動小数点の切り捨てで-0.5を-1に。

    こんにちは。 小数点以下を切り捨てたいときにキャストするために 以下のようにしたところ、 float  i ; for( i=-2.5f; i<3.5f; i+=1.0f ) {   printf( "%f %d\n", i, (int)i ) ; } -2.500000  -2 -1.500000  -1 -0.500000  0 0.500000  0 1.500000  1 2.500000  2 上記の様な結果になりましたが、これを -2.500000  -3 -1.500000  -2 -0.500000  -1 0.500000  0 1.500000  1 2.500000  2 のように-0.5なら-1にするようしたいのですが、 if文は使わずに計算だけで変換することは 可能でしょうか ?

  • コンパイル環境の違いによる、浮動少数点の違い

    不動少数(float)を使った、c言語の演算プログラムを作っています。 Aマシン(CentOS5, gcc3.4)でコンパイルした、"test_a.so"と"test_a.a"について、 Aマシンで実行した結果、両者の結果は一致します。 Bマシン(Fedora5, gcc3.0)でコンパイルした、"test_b.so"と"test_b.a"について、 Bマシンで実行した結果、両者の結果は一致します。 Aマシンで実行した結果、両者の結果が異なります。 結果の比較をすると、以下のようになります。 "test_a.so"=="test_a.a"!="test_b.so"!="test_b.a" (1)コンパイル環境と実行環境が違うと、浮動少数の演算に違いがでることがありますか? (2)コンパイル環境と実行環境が違うと、so と aに違いがでることがありますか? 上記2点について、ご存知の方いらっしゃいましたら教えてください。 お願い致します。

  • 文字列をfloatで読み込む(atoi,sscanf)。しかし、値がおかしい。

    お世話になっています。 C言語の質問です。 文字列をfloatで読み込もうとしているのですが、出力結果がおかしくて困っています。 文字列をatofで変換した場合、doubleでは上手く表示できるのですが、floatでは少数が上手く表示できません。 また、sscanfでも試したのですが、上手く表示できませんでした。 どうしても、doubleを使わずにfloatであらわしたいと考えています。 どうかこのプログラムの問題点のご指摘お願いします。 実行結果 53.196600 53.196602 53.196602 ソース #include<stdio.h> #include<stdlib.h> #include<string.h> main() { char str[100]="53.1966"; double b; float c,d; b = atof(str); printf("%f\n",b); c = (float)atof(str); printf("%f\n",c); sscanf(str,"%f",&d); printf("%f\n",d); } 開発環境 windowsXP cygwin