小数の連続な加算について
小数の連続な加算について
初期値 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
加えるというプログラムを下の様に組みました。
#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
Re: 小数の連続な加算について
こんなプログラムを組んで実験してみました。
これは、0.1を2進数表示するプログラムです。
結果は 0.0001100110011… という循環小数になるはずなのですが、有効桁数というものが生じてしまい、表示結果は途中から0ばっかりになってしまいます。
簡単に言ってしまえば、電卓(Windowsのではなくて、現実の電卓です)で 1/3 を計算すると 0.3333333 ぐらいまでしか求まらないのと同じでしょうか(^^;;
これを足していくので誤差が出るものと思われます(完全な循環小数ならば誤差は出ないはずですので)。
※推測が大半です。間違いがあれば指摘してください。
コード: 全て選択
#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.0001100110011… という循環小数になるはずなのですが、有効桁数というものが生じてしまい、表示結果は途中から0ばっかりになってしまいます。
簡単に言ってしまえば、電卓(Windowsのではなくて、現実の電卓です)で 1/3 を計算すると 0.3333333 ぐらいまでしか求まらないのと同じでしょうか(^^;;
これを足していくので誤差が出るものと思われます(完全な循環小数ならば誤差は出ないはずですので)。
※推測が大半です。間違いがあれば指摘してください。
[hira]
http://hira.hopto.org/
http://hira.hopto.org/
レクありがとうございます。加えてもう一点なのですが…
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の中身はどう評価するべきなんでしょう?
このあたりが、やっぱりまだよく分からないでいるんです。
そうですか。うーん、難しいとこですね…
加えてもう一点、気になることがあるんです。
前のプログラムをループでまとめて(縦長を縮めて)、
下の様にほぼ同様の内容のプログラムを作りました。
#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 "A=";A
Print "比較評価 A=";C;"?";A=C
今度は初期値を「1」にセットして実行してみます。
答えは見事にきっかり「2.5」。
確認のため予め計算しておいた「2.5」(Cの値)と
比較してみると答えは「0 (偽)」…
「2.5」なのに「2.5」と評価できない?!
この場合、変数Aの中身はどう評価するべきなんでしょう?
このあたりが、やっぱりまだよく分からないでいるんです。
BackSeachABを調べていて…
AB掲示板の過去ログ、BackSeachABを調べていて、
「0355 - For~Nextについて」がもしかしたら同様のトピックでは
ないかと思ったのですが、違うでしょうか?
ただいずれにしても、まだ分かりません…
「0355 - For~Nextについて」がもしかしたら同様のトピックでは
ないかと思ったのですが、違うでしょうか?
ただいずれにしても、まだ分かりません…
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を返します。
しかしこれを分数を使わずに計算すると
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を返します。
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
<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/
' ============================================================
' Sinryow Game Home Page - http://www.sinryow.net/
' Sinryow ActiveBasic Center - http://ab.sinryow.net/
' ============================================================
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 にめちゃくちゃ近いだけ。 ってことで合ってますか?(-_-;)
~変数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 にめちゃくちゃ近いだけ。 ってことで合ってますか?(-_-;)
イグトランスさん、Sinryowさん、辻本さん、レクありがとうございます。
辻本さんが書かれていた通り、
「A の値は 2.5 と表示されてるけど実は 2.5 にめちゃくちゃ近いだけ」
ということなんですね。
加算を繰り返してどうというのも、たまたま誤差が目に見える形で
蓄積して表示された結果で、それ以前に「0.1」そのものが、人と
コンピュータでは別の数値を見てる。
Print 0.1=0.1
では、確かに「-1」と返ってくるけど、ただ、自分とコンピュータはそれぞれ
別の数値を比べてる。
内部で数値がどう処理されているか、「0.1」なんて10進の小数は
人間の目にはいかにも明らかな数値なんで、それが二進化されて桁に
収まらない循環小数になり、丸め誤差が生まれて結果に影響するというのは
中々、実数の演算の時は本当に頭に入れとかないといけないですね。
「浮動小数点数を等しいかどうか判定するのは御法度と言われることもあります。」
というのが、実感もって思い知ります。
ありがとうございました。hiraさん、OverTakerさんも改めて。
ようよう気を付けんとなーと念じつつ。
返信遅れてすみません。またよろしくお願いします。
辻本さんが書かれていた通り、
「A の値は 2.5 と表示されてるけど実は 2.5 にめちゃくちゃ近いだけ」
ということなんですね。
加算を繰り返してどうというのも、たまたま誤差が目に見える形で
蓄積して表示された結果で、それ以前に「0.1」そのものが、人と
コンピュータでは別の数値を見てる。
Print 0.1=0.1
では、確かに「-1」と返ってくるけど、ただ、自分とコンピュータはそれぞれ
別の数値を比べてる。
内部で数値がどう処理されているか、「0.1」なんて10進の小数は
人間の目にはいかにも明らかな数値なんで、それが二進化されて桁に
収まらない循環小数になり、丸め誤差が生まれて結果に影響するというのは
中々、実数の演算の時は本当に頭に入れとかないといけないですね。
「浮動小数点数を等しいかどうか判定するのは御法度と言われることもあります。」
というのが、実感もって思い知ります。
ありがとうございました。hiraさん、OverTakerさんも改めて。
ようよう気を付けんとなーと念じつつ。
返信遅れてすみません。またよろしくお願いします。
誤差が生まれないようにする方法もありますよ。
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
という等式になるため、理論的にも正しいです。
うまく出来ました?
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
という等式になるため、理論的にも正しいです。
うまく出来ました?