• ベストアンサー

型変換の問題?

次のプログラムなんですが #include <stdio.h> #include <math.h> #include <cstdlib> #include <iostream.h> void main(){ double v[10000]; for(int b=0;b<10000;b++){ v[b]=2.5+0.01*(double)(rand()%249+1);} for(b=0;b<10000;b++){ int c=(int)(100.*v[b])-251; if(c==192){printf("%d %f\n",c,v[b]);} }} 私の環境のVC++6.0 on win2kのもとで やってみると、printf("%d %f\n",c,v[b])のところで 192 4.43 となったり 192 4.42 となったりします。 真の値は、192 4.43のつもりでプログラムを組んだのですが、 なぜかc=(int)(100.*v[b])-251;という写像は 単射になっていないようです。 さて、質問ですが、double→int型の型変換に原因があるのでしょうか?

noname#108554
noname#108554

質問者が選んだベストアンサー

  • ベストアンサー
回答No.3

実数の計算をするからです。実数がコンピュータ内部でどのように表現されているかご存じですか?データは全て2進数で表されます。このとき、各桁の重みは整数の方は2^nですが、小数は(1/2)^nであらわされ、 0.5,0.25,0.125,0.0625,0.03125,0.015625,... となります。計算過程は、この数字で表現可能でしょうか?例えば、0.01という数字は、すでに表現不可能な数字です。表現不可能な数字は、表現可能な範囲で丸められるので、必ず誤差が生じます。%fを、%.16fとしてみてください。誤差が表示されます。 on Solaris 192 4.4399999999999995 192 4.4299999999999997 %fでの表示上は「4.42」ですが、実際には「4.4299999999999997(まだ続く)」という値で計算がされているということです。これは、 fprintf("%.16f / %.16f\n", 0.0625, 0.1); とかすれば、よくわかると思います。また、doubleからintは縮小変換になるので、何らかの桁落ちが発生します(64ビット→32ビット)。そのあたりでも誤差が発生していると思われます。 #縮小変換はVCではワーニングがでませんか? 精度が欲しいなら、欲しい精度が全て整数になるようにしてから計算するか、1桁ずつ計算するしかありません。探せば、COBOLのように10進数として計算(←用語忘れた)するライブラリがあるかもしれません。 ちなみに、 void main() { int v[10000]; int b; for (b = 0; b < 10000; b++) { v[b] = 250 + 1 * (rand() % 249 + 1); } for (b = 0; b < 10000; b++) { int c = (v[b]) - 251; if (c == 192) { printf("%d %d\n", c, v[b]); } } } 数字がそろいました。

noname#108554
質問者

お礼

そうやって誤差を表示すれば一目瞭然です。 ありがとうございました。 >#縮小変換はVCではワーニングがでませんか? いや、でないです。 しかし、わずか小数点2桁で誤差が 発生するとは驚きです。

その他の回答 (3)

  • ranx
  • ベストアンサー率24% (357/1463)
回答No.4

doubleからintへの変換ですから、どうしたって単射にはなりえないですよね。 (5.2と5.3はどちらも5になる。) printfで%fの表示桁数がこんなに小さいのはどこのライブラリなのか 疑問ですが、それはさておき、原因はキャストとprintfの仕様の違いに あると思います。 キャストは端数を切り捨てて整数化しますが、printfは末尾を四捨五入して 表示します。ですから、例えばv[b]が4.439だったらcは192になりますが、 printf("%.2f",v[b])で表示すれば4.44になります。 4.42は何でしょうねえ。

noname#108554
質問者

補足

>doubleからintへの変換ですから、どうしたって単射にはなりえないですよね。 しまった・・・言い訳です。 すみません、単射の意味をプログラムに合わせて勝手に 頭の中で修正してしていました。 この場合の値域は、実数全体ではなくて2.51~4.99までで 0.01区切りなので、自然数みたいなものです。 いや、やりたいのは、配列のindexに実数はとれないので int型にcastしたいんだけど、精度がね・・・っていうことです。

回答No.2

#1 : > 余談ですが、for文のパラメータの中で変数の宣言を > するのはVC++では認められているのかもしれませんが > 本来は間違いです。 異議アリ。このコードがC++ならば(iostream.hをincludeしているので間違いなくC++でしょう)きわめて合法です。

noname#108554
質問者

お礼

最近、正統的なプログラムをしたいと考えていましたので #1さんの指摘にはどきどきしましたが、問題ないですよね。

  • shige_70
  • ベストアンサー率17% (168/946)
回答No.1

Solaris,gccの環境で試しましたが、4.43と4.44が出ました。 浮動小数点はどうしても誤差がでます。 小数部の切り捨てで誤差が境界を越えてしまったのでしょう。 この場合でしたら、intにキャストする前に0.5を足して四捨五入扱いにするなどで回避するしかないでしょう。 余談ですが、for文のパラメータの中で変数の宣言をするのはVC++では認められているのかもしれませんが本来は間違いです。

noname#108554
質問者

お礼

ありがとうございます。 やっぱり精度の問題みたいです。

関連するQ&A

  • 型変換がうまく出来ない

     今晩は、Cの初心者です、宜しくお願いします。  下のようなコードを書きましたが、正常に表示されません。  一体何処が悪いのでしょう。 =========================================================== #include <stdio.h> #include <string.h> #include <ctype.h> int main(void) { char s2[20] = "0.789"; double ddt = atof(s2) ; printf("ddt = %lf\n\n" , ddt); printf("atof(s2) = %lf\n\n" , atof(s2)); printf("(float)idt + atof(s2)) = %lf\n\n" , ((float)idt + atof(s2))); return 0; }

  • ファイルからのデータ入力

    ファイルから数字を読み取って最初の5文字だけを 出力するプログラムのつもりなんですが、動きません。 VC++6.0で、OSはwin2kです。 ファイルの終わりをどう検出するかの問題だと思いますが、 よく分かりません。教えていただけると助かります。 #include <stdio.h> #include <cstdlib> #include <fstream.h> #include <iostream.h> #include <vector> using namespace std; void main(){ FILE *f; f=fopen("dat.txt","r"); vector <int> a; int b,c; while((c=fscanf("f","%d\n",b))!=EOF){ fscanf("f","%d\n",b); a.push_back(b); } for(int i=0;i<5;i++){ printf("%d",a[i]); } fclose(f); }

  • プログラムがうまくいきません。教えてください。

    プログラムがうまくいきません。教えてください。 #include <stdio.h> int main(void){ int a, b, c, d, f; printf("国語="); scanf("%d", &a); printf("算数="); scanf("%d", &b); printf("理科="); scanf("%d", &c); printf("社会="); scanf("%d", &d); f = ((a>90)?1:0)+((b>90)?1:0)+((c>90)?1:0)+((d>90)?1:0); printf("\n合計[%d],平均[%.2f]\n",(a+b+c+d),((double)(a+b+c+d)/4.0)); printf("国語[%d],[%.3f%%],[%d]\n",a,((double)a/1.5),((a>90)?1:0)); printf("算数[%d],[%.3f%%],[%d]\n",b,((double)b/1.5),((b>90)?1:0)); printf("理科[%d],[%.3f%%],[%d]\n",c,((double)c/1.5),((c>90)?1:0)); printf("社会[%d],[%.3f%%],[%d]\n",d,((double)d/1.5),((d>90)?1:0)); printf("総合判定[%d],\n", ((f>=3)?1:0)); return (0); } と入力したのですが、実行結果の国語から社会までの[]部分を上下揃えたいのですが揃いません。このプログラムにどのようなことを追加すれば揃えることができますか?教えてください!

  • C言語における複素数の四則演算について

    複素数の四則演算(a+biとc+diの四則演算)について、for文を用いて表示するプログラムについて、???の部分に何を入れたらよいかわからず、うまく実行することができません。和・差・積・商の計算種別を入れるみたいなのですが、何を入れたらいいのかわかりません。 #include <stdio.h> void fukuso(double a,double b,double c,double d,double *e,double *f,int keisan); int main(void) { double a=4, b=8, c=4, d=3, e, f; int i; for(i=1;i<5;i++){ fukuso(a,b,c,d,&e,&f,???); if(i==1) printf("和演算\n"); else if(i==2) printf("差演算\n"); else if(i==3) printf("積演算\n"); else printf("商演算\n"); printf("e=%f f=%f i\n",e,f); } return (0); } void fukuso(double a1,double b1,double a2,double b2,double *a3,double *b3,int keisan) { if(keisan==1){ *e=a+c; *f=a+c; } else if(keisan==2){ *e=a-c; *f=b-d; } else if(keisan==3){ *e=a*c-b*d; *f=a*d+c*b; } else{ *e=(a*c+b*d)/(c*c+d*d); *f=(-a*d+c*b)/(c*c+d*d); } }

  • C言語の問題

    以下はC言語の問題です。お教えください。 1000以下の素数を求めるプログラム prog.c を作成せよ。各素数を整数4桁で出力し、15個の素数を出力した時点で改行処理 を行うこと。作成したプログラムを提出せよ。 です。 僕の考えでは、 #include <stdio.h> #include <math.h> main(){ int i; int j; int ix; int k; printf("正の整数を入力して下さい: "); scanf("%d",&i); ix=(int)(sqrt((double)i)); k=0; for(j=2;j<=ix;j++) { if(i%j==0) { k=1; } } if(k==0) { printf("%d は素数です\n",i); } else { printf("%d は素数ではありません\n",i); } となると思うのですが。どうやら違うようです。全然わからないので、正しい答えを教えてください。

  • C++でのプログラムについての質問です

    このような二次関数の解を求めるプログラムを作成したのですが、自作関数solveをvoid solve(double, double, double)のように変更し同じ動作をするように変更したいです どのようにへんこうすればよいでしょうか #include<stdio.h> #include<stdlib.h> #include<math.h> int main(void) { double a, b, c; /*二次方程式の定数*/ double D, x1, x2, r1, r2; printf("ax^2 + bx + c = 0 の係数 a, b, c を入力してください---> \n"); scanf_s("%lf %lf %lf", &a, &b, &c); printf("2次方程式を解いた結果は次の通りとなる。\n"); if (a == 0.0) { if (b == 0.0) { printf("係数がおかしい\n"); exit(-1); } { x1 = -c / b; printf("解は%f です。\n", x1); exit(0); } } else { D = b * b - 4 * a * c; if (D >= 0) { x1 = (-b + sqrt(D)) / (2.0 * a); x2 = (-b - sqrt(D)) / (2.0 * a); if (D == 0.0) { printf("解は %f です。\n", x1); } else { printf("解は %f と %f です。¥n", x1, x2); } } else { r1 = -b / (2 * a); r2 = sqrt(-D) / (2 * a); printf("解は%.2f+%.2fi と%.2f-%.2fi \n", r1, r2, r1, r2); } } return 0; }

  • 警告 W8065について。

    このプログラムを実行したら、実行結果は思い通りになったのですが、 「警告 W8065…プロトタイプのない関数」 と言うのが出ました。  (1)これを消すにはどうすればよいのかを教えてください。  (2)このプログラムはわざと4つに分けているのでこの状態のままプログラムのどこをいじればよいのかを教えてください。 OSはWindows XPでボーランドのコンパイラを使用しています。 #include<stdio.h> int hiki(); int kake(); int waru(); int main(){ int a,b; printf("一つ目の数字\n"); scanf("%d",&a); printf("二つ目の数字\n"); scanf("%d",&b); printf("計%d\n",a+b); hiki(); return 0; } int hiki (){ int c,d; printf("一つ目の数字\n"); scanf("%d",&c); printf("二つ目の数字\n"); scanf("%d",&d); printf("計%d\n",c-d); kake(); return 0; } int kake (){ int e,f; printf("一つ目の数字\n"); scanf("%d",&e); printf("二つ目の数字\n"); scanf("%d",&f); printf("計%d\n",e*f); waru(); return 0; } int waru (){ int g,h; printf("一つ目の数字\n"); scanf("%d",&g); printf("二つ目の数字\n"); scanf("%d",&h); printf("計%d\n",g/h); return 0; }

  • visualC++を使って、

    visualC++を使って、 ---------------------- 上線の長さは?:8 下線の長さは?:12 縦線の長さは?:3 太さは?:2   ********   ********      **      **      ** ************ ************ ---------------------- ・・・と入力した値に対して「*」を使って「工」という文字の図形が現われるプログラムを作る課題なのですが・・・ とりあえず、試行錯誤をした結果 #include <stdio.h> void main() { int a,b,c,d,e,f,g,h; printf("上線の長さは?"); scanf("%d", &a); printf("下線の長さは?"); scanf("%d", &b); printf("縦線の長さは?"); scanf("%d", &c); printf("太さは?"); scanf("%d", &d); for(e=1; e<=a; e++){ for(h=1;h<=d; e++) printf("*"); printf("\n"); } for(f=1;f<=c; f++){ for(h=1;h/2<=d; h++) printf(" "); for(h=1;h<=d; h++) printf("*"); } for(g=1; g<=b; g++){ for(h=1; h<=d; h++) printf("*"); printf("\n"); } と書いたのまではいいのですが、いざデバッグすると*が無限に現われてしまいます・・・。 C言語なんて今までやったことがない自分には難しくて・・・どうかご教授お願いします。

  • ビルドが失敗してしまいます

    最近C言語を勉強を始めまして、参考書に載っていた以下のソースプログラムをvisualC++2010に打ち込んだのですがビルドが成功しません #include <stdio.h> #include <math.h> int main(void) { int d[33]; double m,v,s; int n=33; int i; double sum; printf("データを%d個入力してください\n",n); for(i=0;i<n;i++) { printf("date %d=",i+1); scanf_s("%d",&d[i]); } for(sum=0.0,i=0;i<n;i++) { sum+=d[i]; } m=sum/n; for(sum=0.0,i=0;i<n;i++) { sum+=(d[i]-m)*(d[i]-m); } v=sum/n; s=sqrt(v); printf("平均=%10.3f\n",m); printf("分数=%10.3f\n",v); printf("標準偏差=%10.3f\n",s); return 0; } エラーメッセージは ・'scanf' の宣言を確認してください。 ・(11): warning C4566: ユニバーサル文字名 '\u00A5' によって表示されている文字は、現在のコード ページ (932) で表示できません ・(14): warning C4566: ユニバーサル文字名 '\u00A5' によって表示されている文字は、現在のコード ページ (932) で表示できません ・(14): error C2065: 'i1' : 定義されていない識別子です。 と出るのですがさっぱりわかりません。どなたか教えてください。

  • 数学の公式に沿った逆行列

     数学の公式に沿った2x2逆行列を作るという演習問題を解きたいのですが  下記のプログラムではdの値がおかしくなってしまいます。  何が悪いのか指摘していただけませんでしょうか。  よろしくお願いします。 // 20060414.cpp : コンソール アプリケーションのエントリ ポイントを定義します。 // #include "stdafx.h" #include <stdio.h> #include <stdlib.h> int _tmain(int argc, _TCHAR* argv[]) { int i,j ; double a[2][2] ; double b[2][2] ; double d ; for (i=0;i<2;i++){ for (j=0;j<2;j++){ printf("a%d%d = ?",i+1,j+1); scanf("%f",&a[i][j]); } } d = a[0][0]*a[1][1]-a[0][1]*a[1][0]; if (d==0.0){ printf("行列式は正則でない。\n"); exit(1); } b[0][0] = a[1][1]/d; b[0][1] = -a[0][1]/d; b[1][0] = -a[1][0]/d; b[1][1] = a[0][0]/d; for (i=0;i<2;i++){ for (j=0;j<2;j++){ printf("b%d%d = %f\n",i+1,j+1,b[i][j]); } } scanf("%d",d); return 0; }

専門家に質問してみよう