Document

コンピュータリテラシ 第14回
MATLAB(2b)
コンピュータでの数値計算:やってはいけない計算
今日のテーマ:
コンピュータによる数値情報の扱い
二進数による実数の表現
を見てみよう!
2進の浮動小数点による数の表現
>> f=[1 1];
>> f .* 0.5 .^(1:length(f))
>> sum(f .* 0.5 .^(1:length(f)))
0.11(2)= (1/2)+(1/2)^2
>> f=[0 1 0 1 0 1];
>> f .* 0.5 .^(1:length(f))
>> sum(f.*0.5.^(1:length(f)))
0.010101(2)
コンピュータ内部での実数は、2進の指数表現。
1.1(2) × 2-1
仮数部が、「1.-----」(小数点より左が必ず1になるよ
うな形)に正規化した後に、数の大きさを指数部で表
している。
1.0101(2) × 2-2
IEEE754規格の倍精度(64bits)実数
1bit
0
11bits
52bits
00・・・・・・0
00・・・・・・・・・・・・・・・・・・・・・・・・・・・・・0
仮数部 : 2進52桁は、10進数の15桁くらいの有効数字
指数部(符号含む)
仮数部の符号
符号は、1のとき負の数
では、いよいよ、
やってはいけない計算、について
自分で確かめる
浮動小数点による数の表現の限界による誤差
オーバーフロー、アンダーフロー
>> realmax
最大の数
>> realmin
最小の数
realmaxやrealminの周辺で起きる
>> (1e150)^2
>> (1e200)^2
無限大
>> 1/Inf
ゼロ
オーバフローとアンダーフローを観察
>> (1e-150)^2
>> (1e-200)^2
ゼロ
>> 1/(1e-200)^2
無限大
実数の丸め誤差による計算結果の狂い
>> x=1;
<= x=1からはじめ
て、 1+xが1と等し
くない間xを半分に
していく。
>> bitex(0.1)
<= 関数bitexは仮数部(有効
桁部分、2進の小数52桁)の2
進表現を求める。
>> while (1+x ~= 1)
ans =
x=x/2;
1001100110011001100110011001100
110011001100110011010
end
<= 0.1は、無限小数なので、有効桁数で丸め
られることが観察できる。
>> x
x=
1.1102e-016
<= While文が停止した時のxの値(数学的にはどんなにxを1/2しても1+x
とxは等しくならないはずだが) このwhile文は誤差のおかげで止まる
0.1の二進表現に係わる
計算の狂い
>> x=0.1; disp(x);disp(bibtex(x));
>> while x<1
x = x + 0.1; disp(x);
disp(bitex(x));
0.500000000000000
0000000000000000000000000000000000000000000000000
000
end
(省略)
0.200000000000000
0.900000000000000
10011001100110011001100110011001100110
01100110011010
110011001100110011001100110011001100110011001100110
0
0.300000000000000
1.000000000000000
00110011001100110011001100110011001100
11001100110100
0.400000000000000
10011001100110011001100110011001100110
01100110011010
1111111111111111111111111111111111111111111111111111
1.100000000000000
00011001100110011001100110011001100110011001100110
01
fl.m
• 関数flは、数値xを10進の有効桁数5桁に丸め
る関数である。
使用例:
>> pi
>> format long
>> pi
>> fl(pi)
←有効桁5桁
• 倍精度実数計算ではだいたい10進数16桁の
有効桁数であるが、何桁であっても有限桁数
が決まっていればそれによる誤差が生じること
を関数flを使って観察しよう。
有効桁数に伴う情報落ち
>> x1=121730; x2=3.1481;
>> x1+x2
ans =
1.217331481000000e+005
有効桁数16桁の正しい答え
>> fl(x1+x2)
ans =
121730
赤: 誤差の大きな結果
有効桁数が5桁しかないと、結果は
こうなる。x2の値は無いのと同じ。
有効桁限界あたりの怪しい計算
例: 2数の平均
>> a=8.1651; b=8.1653;
>> (a+b)/2
ans =
8.16520000000000
>> fl(fl(a+b)/2)
ans =
正しい答え
同じ計算を有
効桁数5桁で
すると、誤差
が大きい
平均値であり得ない値!
8.164999999999999
赤: 誤差の大きな結果
>> fl(a+fl(fl(b-a)/2))
ans =
8.165200000000001
有効桁数5桁でも
工夫によって正し
い答え
桁落ち(ごく近い2数の差で有効桁消失)
例: 2次方程式の解
𝑏 2 − 4𝑎𝑐 = 123.98
>> a=1; b=-124;c=1;
>> (-b+sqrt(b^2 - 4*a*c))/(2*a)
ans =
1.239919349593155e+002
>> (-b-sqrt(b^2 - 4*a*c))/(2*a)
ans = 0.008065040684528
>> fl(fl(124+123.98)/2)
ans =
1つの解は大体正しいが、もうひとつの解
は誤差が大きい
1.239900000000000e+002
>> fl(fl(124-123.98)/2)
赤: 誤差の大きな結果
ans = 0.010000000000000
>> fl(1/123.99)
計算の工夫で正しい解
正しい2つの解
ans = 0.008065200000000
緑: 誤差許容範囲の結果
2次方程式の2つの
解をαとβとすると、
β=c/α*a
を使
用
桁落ち(ごく近い2数の差)
例: 分子の有理化
>> x=0.0012345;
>> sqrt(1+x)-1
𝒙 = 𝟎. 𝟎𝟎𝟏𝟐𝟑𝟒𝟓のとき
𝟏+𝒙 − 𝟏 を求める
有効桁5桁でも計算の工夫
正しい答え
𝑥
1+x +1
を求める
ans = 6.170596187133892e-04
>> fl(1+x)
>> fl(1.0006+1)
ans = 1.001200000000000
ans = 2.000600000000000
>> fl(sqrt(1.0012))
>> fl(0.0012345/2.0006)
ans = 1.000600000000000
ans =
>> fl(1.0006-1)
ans =
誤差が大きい
6.000000000000000e-004
6.170600000000000e-004
中間結果のオーバフロー
>> a=2.0e200;
>> s=max(abs([a b]));
>> b=1.0e200;
>> c=s*sqrt((a/s)^2 + (b/s)^2)
>> c=a^2 + b^2
c=
c=
Inf
cは非常に大きな値のため
オーバーフロー
2.236067977499790e+200
オーバーフローを避ける計算の工夫
S=max(|a|,|b|)
𝑎
C= 𝑎2 +𝑏 2
𝑏
C = s* ( 𝑠 )2 +( 𝑠 )2
やってはいけない計算のまとめ
1+ε=1であるようなε>0が存在する(!!)
• 小さい数を足すと小さい数の情報が失われる
ことを『情報落ち』という
• たとえば
1.0000000000000000+0.0000000000123456789 だと、
本当はまったく1にはならないはずが
1.0000000000123456 のようになって、有効桁
数より後ろの部分の情報は勝手に失われる)
実数で数えてはいけない
たとえばプログラミング言語で、ある数を0からはじめて1になるまで0.1を足すとこんなこと
が起きる。
1回目
0.10000000000000000000
2回目 0.20000000000000000000
3回目 0.30000000000000004000
4回目 0.40000000000000000000
5回目 0.50000000000000000000
6回目 0.60000000000000000000
7回目
0.70000000000000000000
8回目
0.79999999999999990000
9回目 0.89999999999999990000
10回目 0.99999999999999990000 ← 10回足しても1にならないから11回やる
11回目 1.0999999999999999000
・このように、0.1を足していくと微妙に誤差がある。
・このためちょうど1にならず、11回足してしまう。
・逆に「0.333333333333333333」だと3回で1になる。
・教訓: 決まった回数実行するときは実数で数えてはいけない
実数で数えてはいけない
• 根本的な原因は、0.1という数値が2進法だと
正確に表せず、近似値になること。
• もともと、コンピュータでの実数計算はすべて
近似値だと思え!
• 近似値計算による誤差のことを「丸め誤差」と
呼ぶ
数式として等しくても
計算に向く式と向かない式がある
• 例: 「√(1+x) - 1」を計算したいとする。xは0にか
なり近い。
• 方法1: そのまま計算
「 √(1.0 + x) - 1.0」
• 方法2: 元の式の分子と分母に「√(1+x) + 1」を
掛けて整理
「x / (√(1.0 + x) + 1)」
数式として等しくても計算に向く式と向かない式がある
たとえば、x = 0.0000000000002 だとする。
1+x = 1.000000000000002 だから
√(1+x) ~ 1.000000000000001 のはず(展開して確認しよう)
つまり答え ~ 0.000000000000001 のはず
あるプログラミング言語で実際に計算した結果
方法1: 8.8817841970012520000e-16
方法2:
9.9999999999999970000e-16
・方法1(素朴に計算)→有効桁の値がデタラメになる
・方法2(変形してから計算)→有効数字16桁で合っている
・何が起きているのか? たとえば、16桁の精度で表された非常に近い2数を引き算
1.0000000000123456
-) 1.0000000000123333
--------------------0.0000000000000123
・最初は有効桁数16桁あったはずが→計算後は3桁しかない!
数式として等しくても
計算に向く式と向かない式がある
• 非常に近い2数を引き算すると有効桁数が激
減すること→『桁落ち』と呼ぶ
• 桁落ちを起こさないためには、近い2数の引き
算を避ける(この例題での変形のようにして)
級数による計算
• 数値計算では、級数による計算をすることも多
い。
• 例: π = 4 - (4/3) + (4/5) - (4/7) + (4/9) - ...
無限級数をn項求めることで円周率を求められ
るか、あるプログラミング言語で計算してみた
ところ
級数による計算
public class Test {
public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
double pi = 4.0;
double sign = -4.0;
for(int i = 0; i < n; ++i) { pi += sign / (2.0*i + 3.0); sign = -sign; }
System.out.printf("%.20g¥n", pi);
}
}
> java Test 10
: n=10項まで計算
3.2323158094055940000
> java Test 100
: n=100項まで計算
3.1514934010709914000
> java Test 1000
: n=1000項まで計算
3.1425916543395442000
級数による計算
• 級数を計算するとき、無限にはできないので適
当なところで打ち切る
→打ち切った残りは誤差になる
→『打ち切り誤差』
• 打ち切り誤差を小さくするには
→計算する項を増やす(遅くなる)、
→収束の速い級数を使用する(正しい方向、
ただし常に可能とは限らない)
以上のような理由から、
コンピュータによる計算結果が真の値から
狂ってしまうことがあることを知っておく。
MATLABのライセンス
• 東工大生のMATLABの利用:
TAH(Total Academic Headcount)ライセンス
http://wwwnode/39.gsic.titech.ac.jp/