プレイ日記
おちゃめ ochame_nako
このプログラムは0.1秒ごとに変数Tの値を表示していき10秒で終了するというプログラムです。 実はこのプログラムには致命的なバグがあるのですがどこでしょうか? (解答と解説はコメントに書いています)
9そうだね
プレイ済み
返信[1]
親投稿
おちゃめ ochame_nako
実行してみたら分かりますが、実はこのプログラムは10秒経っても停止しません。 プチコン3号で正常動作しないときはDIRECTモードで変数の値を表示して想定通りに動作しているかどうかの確認を行うのがポピュラーな方法です。 試しに「10」と表示された瞬間にプログラムを停止させて PRINT T もしくは ?T としてみると変数Tの値はちゃんと10になっているためこのIFが動作しないのは原因が分かりません。
1そうだね
プレイ済み
返信[2]
親投稿
おちゃめ ochame_nako
その理由はプチコン3号はすべての数値が内部では2進数で表現されているためです。 10進数で10は2進数だと1010になるわけですが、10進数で0.1は2進数だと0.0001100110011…という循環小数になります。 実数型で採用されている倍精度浮動小数点は符号部1bit、指数部11bit、仮数部52bitの64bitとなるため0.1はこのようになります。 0011111110111001100110011001100110011001100110011001100110011010 (私が作ったBIN64$関数を使えば10進数を64桁の2進数に変換できます)
2そうだね
プレイ済み
返信[3]
親投稿
おちゃめ ochame_nako
無限に続く循環小数を52桁で表現したら値が変わってくるのは当然でその結果として0.1を加算する処理を行えば誤差が出てきます。 画面上で10と表示されている時には実は9.9999999999999805になっているのです。 これは普通にDIRECTモードで確認することはできませんが、FORMAT$を使うか、私の自作関数であるPSTR$を使えば確認ができます。 実数型で小数演算をした場合には誤差が発生するため等号で判定しては誤動作をしてしまう可能性があるわけです。
1そうだね
プレイ済み
返信[4]
親投稿
おちゃめ ochame_nako
では、どうするかというとこんな感じで一定範囲内の誤差を許容してやれば良いです。 T=0 WHILE 1  T=T+0.1  PRINT T  IF ABS(T-10)<0.05 THEN PRINT "10びょうたちました!":END  WAIT 6 WEND DIRECTモードで確認できないレベルの小さな誤差のみ許容するならば0.05の部分を5E-9とすれば良いです。
1そうだね
プレイ済み
返信[5]
親投稿
おちゃめ ochame_nako
誤差というのはどんどん積み重なっていくためどの程度許容するかはかなり難しいです。 それならば、誤差が出ないようにすれば良いですね。 誤差がでないようにする最も簡単な方法は整数のみによる演算を行うことです。
2そうだね
プレイ済み
返信[6]
親投稿
おちゃめ ochame_nako
Tを0.1ではなく1ずつ加算して表示の段階で10で割れば誤差出ないので等号で判定が可能です。 T=0 WHILE 1  T=T+1  PRINT T/10  IF T==100 THEN PRINT "10びょうたちました!":END  WAIT 6 WEND このプログラムでは3カ所の変更で済みましたが、規模が大きなプログラムならば変更にが必要になるでしょう。
0そうだね
プレイ済み
返信[7]
親投稿
おちゃめ ochame_nako
簡単に対策できる他の方法としては私のPR関数を使うという方法があります。 T=0 WHILE 1  T=PR(T+0.1)  PRINT T  IF T==10 THEN PRINT "10びょうたちました!":END  WAIT 6 WEND PR関数はDIRECTモードでの表示と同じ値になるように丸める関数なのでこれで10と表示されていればその値も10になっているので等号で判定が可能だし1カ所のみの変更で済みます。
0そうだね
プレイ済み
返信[8]
親投稿
おちゃめ ochame_nako
「小数を扱うから実数型を使う」「デフォルトで実数型になっているから実数型を使う」という感じで特に気にせず実数型を使っている人も多いかと思います。 しかし、このように小数を使用する場合には思わぬ誤動作をしてしまう場合があるため初心者の方で「(DIRECTモードなどで変数の表示を行い)いくら確かめてもプログラムがうまく動作しない」という場合はこういう要因もあるというのを頭に入れておくと良いかもしれません。 もっとも、浮動小数点とはどのような問題があり、DIRECTモードでは有効桁数がどの程度あるかを熟知している人ならばあえて言うまでもない当たり前の話だと思いますが。
2そうだね
プレイ済み
返信[9]
親投稿
りょうま R-S1437
少数を使った時に、IFがきちんと動作しない理由が分かって、少しすっきりしました。
1そうだね
プレイ済み
返信[10]
親投稿
reji Satoshi.1103
小数を2進数で表そうとしたらそうなると初めて知ったので良かったです♪
1そうだね
プレイ済み
返信[11]
親投稿
おちゃめ ochame_nako
りょうまさんへ 「(0.1のような場合であっても)小数は基本的に誤差が出るもの」と考えておけば難しいことは何もありません。 サトシзıо4さんへ 整数であろうと小数であろうと10進数を2進数に変換するには2の累乗の和の形に変換するだけで良いので簡単です。 0.1=0/2+0/4+0/8+1/16+1/32+0/64+0/128+1/256+1/512+0/1024+0/2048+1/4096+1/8192+0/16384+0/32768+1/65536+1/131072…という風になるため0.1が2進数では循環小数になることは分かります。(分母の2、4、8、…はすべて2の累乗)
3そうだね
プレイ済み
返信[12]
親投稿
よっぱらい yopparai1965
仕事では、実数型を使うのはリスキーなので、できる限り整数型を用いるようにしている...
1そうだね
プレイ済み
返信[13]
親投稿
というわけで私はDEFINT主義者かなw
0そうだね
プレイ済み
返信[14]
親投稿
よっぱらい yopparai1965
ついでだから、聞いておこう... 0.1×10が1.0にならないのhば良いとして、1.0×10は10.0になるのかな? (理論上はなるはず...)
0そうだね
プレイ済み
返信[15]
親投稿
おちゃめ ochame_nako
あまさとしおんさんへ 整数をメインで使うならばそれがベターな方法ですね。 よっぱらいさんへ プチコン3号では定数1を「1」と記述したら整数型、「1.0」と記述したら実数型になるのですが、実数型の1.0という定数は循環小数ではなく正しく1を示しています。 したがって、1.0*10で10.0(実数型の10)という値を得られます。
1そうだね
プレイ済み
返信[16]
親投稿
しりゅう shiryu1021
FORを使えば?
0そうだね
プレイ済み
返信[17]
親投稿
おちゃめ ochame_nako
しりゅうさんへ プチコン3号では小数を含む計算はすべて実数型で行われているため同じ問題を抱えています。 FOR I=0.1 TO 1.5 STEP 0.1 A=A+1 ?A NEXT これは本来は15回のループ(変数Aの値は1から15に変化)ですが、実数型の誤差によって14回ループになってしまっています。
0そうだね
プレイ済み
返信[18]
親投稿
しりゅう shiryu1021
ループだけでよければ FOR I=0 TO 15 とすればよいのでは?
0そうだね
プレイ済み
返信[19]
親投稿
おちゃめ ochame_nako
しりゅうさんへ このトピックスは「プチコン3号の実数型は0.1という見た目でキリが良い数字でも誤差が発生するため思わぬ誤動作をしてしまう」というのを知って貰うために用意しました。 その誤差を回避する方法もこのトピック内でいくつか書いていますが最も簡単な方法は実数型(小数を含む値)を使わず整数のみで処理をすることですね。(整数のみの四則演算でも演算結果が整数でで収まらない場合もあるため注意が必要) 今回の件について詳しく知りたい場合は「プチコン3号 実数型 誤差」などのキーワードでWeb検索してみてください。
0そうだね
プレイ済み