小数の連続な加算について

ActiveBasicでのプログラミングでわからないこと、困ったことなどがあったら、ここで質問してみましょう(質問を行う場合は、過去ログやWeb上であらかじめ問題を整理するようにしましょう☆)。
返信する
メッセージ
作成者
jacoby

小数の連続な加算について

#1 投稿記事 by jacoby »

 初期値 J=4 に 0.1を繰り返し15回
加えるというプログラムを下の様に組みました。

#prompt

Dim J As Double
J=4

J=J+.1
J=J+.1
J=J+.1
J=J+.1
J=J+.1
J=J+.1
J=J+.1
J=J+.1
J=J+.1
J=J+.1
J=J+.1
J=J+.1
J=J+.1
J=J+.1
J=J+.1

Print J
Print 5.5=J

 答えは 4+0.1*15=5.5となりそうに思えたのですが
実行結果は 5.499999999…となり、5.5との比較では
0(偽)となってしまいます。
 元々、For-Nextのループで足していて起こり、念のため
ループを外して15回列記して上のように書いてみたのです
が(リストが縦長になってすみません)、同様の結果でした。
 また、初期値のJ=4ですが、他にも 「5」,「6」,「7」,「8」でも
同じような結果になるようです。
 これらの演算、何故そうなるのか理由をお知りの方が
おられましたら是非教えてください。

             実行環境 AB ver 4.04
(ver 2.62でも確認)
OS Windows Me
OverTaker
記事: 231
登録日時: 2005年5月31日(火) 17:14
お住まい: 茨城県

#2 投稿記事 by OverTaker »

コンピューターは、2進法で計算しています。
そのため0.1は2進法で表すことができないために、0.1の近似値を足していっているため計算結果があのようになってしまいます。

私に説明できるのはこれくらいまでです。お互いもっと勉強しなくちゃならないですね~。
hira
記事: 203
登録日時: 2005年5月31日(火) 20:14
お住まい: 兵庫県
連絡する:

Re: 小数の連続な加算について

#3 投稿記事 by hira »

こんなプログラムを組んで実験してみました。

コード: 全て選択

#console
Dim a As Double
Dim Div As Long
Dim Bin As String
a=0.1
Bin="0."
For Div=1 To 200
	If a>=2^-Div Then
		Bin=Bin & "1"
		a=a-2^-Div
	Else
		Bin=Bin & "0"
	End If
Next
Print Bin
これは、0.1を2進数表示するプログラムです。
結果は 0.0001100110011… という循環小数になるはずなのですが、有効桁数というものが生じてしまい、表示結果は途中から0ばっかりになってしまいます。
簡単に言ってしまえば、電卓(Windowsのではなくて、現実の電卓です)で 1/3 を計算すると 0.3333333 ぐらいまでしか求まらないのと同じでしょうか(^^;;
これを足していくので誤差が出るものと思われます(完全な循環小数ならば誤差は出ないはずですので)。
※推測が大半です。間違いがあれば指摘してください。
jacoby

レクありがとうございます。加えてもう一点なのですが…

#4 投稿記事 by jacoby »

OverTakerさん、hiraさん、レクありがとうございます。

そうですか。うーん、難しいとこですね…

 加えてもう一点、気になることがあるんです。

 前のプログラムをループでまとめて(縦長を縮めて)、
下の様にほぼ同様の内容のプログラムを作りました。

#prompt
Dim I As Long
Dim A,B,C As Double
B=1 '= 初期値
C=B+.1*15 '= 合計値
A=B ' 加算用の変数Aに初期値をセット

For I=0 to 14
A=A+.1
Print A
Next I

Print
Print "A=";A
Print
Print "比較評価 A=";C;"?";A=C

 今度は初期値を「1」にセットして実行してみます。
答えは見事にきっかり「2.5」。
 確認のため予め計算しておいた「2.5」(Cの値)と
比較してみると答えは「0 (偽)」…
「2.5」なのに「2.5」と評価できない?!
この場合、変数Aの中身はどう評価するべきなんでしょう?

 このあたりが、やっぱりまだよく分からないでいるんです。
jacoby

BackSeachABを調べていて…

#5 投稿記事 by jacoby »

AB掲示板の過去ログ、BackSeachABを調べていて、
「0355 - For~Nextについて」がもしかしたら同様のトピックでは
ないかと思ったのですが、違うでしょうか? 
 ただいずれにしても、まだ分かりません…
イグトランス
記事: 899
登録日時: 2005年5月31日(火) 17:59
お住まい: 東京都
連絡する:

#6 投稿記事 by イグトランス »

 1 ÷ 3 × 3 = 1と思いますよね。
しかしこれを分数を使わずに計算すると
1 / 3 * 3
= 0.333333...... * 3
= 0.333333...... + 0.333333...... + 0.333333......
= 0.999999...... (あれ?
となってしまいます。

 先に皆さんがおっしゃっていましたとおり、0.1をコンピュータが使う2進数で表すと上の0.333333...の如く小数点以下が無限に続く数になってしまいます。
なのでコンピュータで.1を足し続けたときにもこれと同じことが起こり、jacobyさんの場合、5.499999999......のようになってしまうのです。

なので浮動小数点数を等しいかどうか判定するのは御法度と言われることもあります。

追記 11:20頃
 Windowsの電卓は意外と賢く内部では数値を分数の形で保持している(とヘルプに書いてある)ので、1 / 3 * 3は1を返します。
Sinryow
記事: 141
登録日時: 2005年5月31日(火) 09:34
お住まい: 北海道
連絡する:

#7 投稿記事 by Sinryow »

Single値は,以下のように表現されます。
<sgn(1bit)>×(2^<pow(8bit)><man(23bit)>
sgnは符号を表す1bit,powは2の何乗かを表す8bit(実際には127が引かれる),manは実際の値の部分を表す23bit(実際は"1.<man>"の形をしている)です。
例えばsgn=0(正),pow=129(2の2乗),man=110000...000(2進法の「1.11」=10進法の「1.75」)とすると,それが表す値は 1×(2^2)×1.75=7.0 ということになります。

ちなみにDoubleでは
<sgn(1bit)>×(2^<pow(11bit)><man(52bit)>
です。

Doubleでは2進法で52桁ぶんしか表現できないわけです。

参考記述:
http://ja.wikipedia.org/wiki/%E6%B5%AE% ... 2.E5.BC.8F
' ============================================================
' Sinryow Game Home Page - http://www.sinryow.net/
' Sinryow ActiveBasic Center - http://ab.sinryow.net/
' ============================================================
辻本一揃

#8 投稿記事 by 辻本一揃 »

Double型の計算は難しくてできないんですけど、こういうことですよね。

~変数Cの計算~
2進数では   10進数では
 1.00000000   1.0
+)1.10000000  +)1.5
------------  -----
10.10000000   2.5

10.10000000 は 2.5 だから予定通りの値

~変数Aの計算~
2進数では   10進数では
 1.00000000   1.0
+)0.00011001  +)0.1
+)0.00011001  +)0.1
+)0.00011001  +)0.1
+)0.00011001  +)0.1
+)0.00011001  +)0.1
+)0.00011001  +)0.1
+)0.00011001  +)0.1
+)0.00011001  +)0.1
+)0.00011001  +)0.1
+)0.00011001  +)0.1
+)0.00011001  +)0.1
+)0.00011001  +)0.1
+)0.00011001  +)0.1
+)0.00011001  +)0.1
+)0.00011001  +)0.1
------------  -----
10.01110111   2.5?

10.01110111 は 2.46484... だから計算は合わない


Double型はもっと緻密な計算をしてるだろうけどそれでも 2.499999... で 2.5 とはちょっと違う。だから比較すると偽を返す。A の値は 2.5 と表示されてるけど実は 2.5 にめちゃくちゃ近いだけ。 ってことで合ってますか?(-_-;)
ゲスト

#9 投稿記事 by ゲスト »

イグトランスさん、Sinryowさん、辻本さん、レクありがとうございます。

 辻本さんが書かれていた通り、
「A の値は 2.5 と表示されてるけど実は 2.5 にめちゃくちゃ近いだけ」
ということなんですね。

 加算を繰り返してどうというのも、たまたま誤差が目に見える形で
蓄積して表示された結果で、それ以前に「0.1」そのものが、人と
コンピュータでは別の数値を見てる。

 Print 0.1=0.1

では、確かに「-1」と返ってくるけど、ただ、自分とコンピュータはそれぞれ
別の数値を比べてる。
 
 内部で数値がどう処理されているか、「0.1」なんて10進の小数は
人間の目にはいかにも明らかな数値なんで、それが二進化されて桁に
収まらない循環小数になり、丸め誤差が生まれて結果に影響するというのは
中々、実数の演算の時は本当に頭に入れとかないといけないですね。
 「浮動小数点数を等しいかどうか判定するのは御法度と言われることもあります。」
 というのが、実感もって思い知ります。

 ありがとうございました。hiraさん、OverTakerさんも改めて。
ようよう気を付けんとなーと念じつつ。
返信遅れてすみません。またよろしくお願いします。
jacoby

名前書いてませんでした。

#10 投稿記事 by jacoby »

 すみません。名前書き忘れてました。上のゲストの返信は
質問者jacobyが書きました。改めて。
konisi
記事: 893
登録日時: 2005年7月25日(月) 13:27
お住まい: 埼玉県東松山市
連絡する:

誤差が生まれないようにする方法もありますよ。

#11 投稿記事 by konisi »

0.1を100回足す場合

before code

#N88BASIC
Dim A As Double
Dim I As Long
A=0
For I=1 To 100
A=A+0.1
Next I
Print A

結果:A=9.99999999999998

after code

#N88BASIC
Dim A As Double
Dim I As Long
A=0
For I=1 To 100
A=A+(0.1*10)
Next I
A=A/10
Print A

結果:A=10

やっていることの説明
before codeでは、Aに0.1を100回足してる為、誰かが述べたように誤差が生まれて僅かに10に届かない結果になりました。
after codeでは、Aに0.1の10倍を100回足したあと、Aを10で割っているので、整数値を100回足しても誤差は生まれず、それを10で割っても小数にならなければたいした誤差も生まれないという結果になります。(少数になると、いつものように僅かな誤差が生まれます。)

数学の数式で言うと

0.1*100=(0.1*10)*100/10

という等式になるため、理論的にも正しいです。

うまく出来ました?
イグトランス
記事: 899
登録日時: 2005年5月31日(火) 17:59
お住まい: 東京都
連絡する:

#12 投稿記事 by イグトランス »

それだったら整数型を使えばより誤差が減ります。
固定小数点数として使うわけです。
konisi
記事: 893
登録日時: 2005年7月25日(月) 13:27
お住まい: 埼玉県東松山市
連絡する:

#13 投稿記事 by konisi »

あぁ、なるほど。
返信する