← |
2024年11月 |
→ |
日 |
月 |
火 |
水 |
木 |
金 |
土 |
|
|
|
|
|
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
|
26 |
27 |
28 |
29 |
30 |
|
寝る前に仕事の備忘録。
コンピューターの浮動小数点演算は2進数の丸め誤差が発生する。
簡単に説明すると、 ・二進数表現で0.1は十進数表現で0.5のこと。 ・二進数表現で0.01は十進数表現で0.25のこと。 ・二進数表現で0.11は十進数表現で0.75のこと。 つまり、0以下の各桁の重みは、 1/2,1/4,1/8・・・1/(2^n) なわけで。
十進数表現の0.1を二進数表現で表そうとしたら、 0.0001100110011・・・(※二進数表現。以下、このように特に断りがなければ十進数表現) となる。つまり0011が繰り返される循環小数になるわけだ。
でも、無限に循環するわけにはいかないので、その辺のなんやかんやで、 演算誤差が出るわけだ。
つまり、プログラムの世界において、 0.1を10回足した物と、1は必ずしも一致しない。
さらに、やっかいなのが、1000円の消費税を計算しようと、 double x = 0.05f; double y = 1000f; double z = ceil(x*y); ってやると、zには「51」って入ってしまう。
問題の部分は、「double x = 0.05f」で、単精度の浮動小数点の0.05を、 倍精度に代入して演算をすると、単精度で発生した誤差が、 倍精度できちんと計算されるため、 x*yの結果が50をほんのわずか上回った数字になってしまうので、 切り上げたら51になっちゃうわけだ。
※逆に1/3は十進数では、0.3333・・・と循環するけど、 三進数なら、0.1(※三進数表現)となって、丸く収まる。
このn進数間の誤差を吸収する方法が、固定小数点演算だ。 言語によって、Decimal型とかNumeric型とかCurrency型とか、 いろいろあるけど本質は同じ。
たとえば、0.05を表すには、 5×(10^-2) みたいに、 x×(10^-y) って形で、計算される。
これにより二進数の小数点以下の表現における演算誤差を発生させなくしているわけだ。
で、以下、備忘録の本題。 久しぶりなので、すっかり誤差の範囲を忘れていたので、 せっかくなので書き出してみる。
基本的に、これらの型はなので、Currency型=通貨型と 呼ばれることからも分かるように、会計演算で使う型だ。
なので、通貨計算に限って、正しい演算結果を保証できる桁数の値を書き出してみる。 というか、これらの型が内部でどのように状態を遷移させて、 正確な数値演算をしているかの説明。
■足し算 1+1=2 (※つまり、小数点以下が無し) 0.1+0.2=0.3 (※つまり、小数点以下が1桁まで有効) 0.1+0.05=0.15 (※つまり、演算子の右と左オペランドの小数点以下の桁数の大きい物が有効)
■引き算 ※足し算と同じ。
■かけ算 1234*2=2468 (※つまり、小数点以下が無し) 1234*0.01=12.34 (※つまり、小数点以下が2桁まで有効) 1234*0.05=61.70 (※つまり、小数点以下が2桁まで有効) 0.01*0.02=0.0002 (※つまり、小数点以下が4桁まで有効) 0.01*0.002=0.00002 (※つまり、小数点以下が5桁まで有効) まとめると、かけ算の右オペランドの有効な桁数+右オペランドの有効の桁数。
■割り算 会計演算ではなかなか発生しないけど一番ややこしい。 演算結果によって、有効な桁が無限になる。 1/3=0.333333333・・・
ただ、ちゃんと割り切れるならば、 1/10=0.1 0.1/10=0.01 0.01/0.1=0.1 となる。 これは x×(10^-y) の表現のyの値(スケール)を元に計算する。 すなわち、演算後の有効な桁数は、 (割られる数のスケール)-(割る数のスケール)。
先の例ならば、 1/10=0.1 (※0-1=-1なので10^-1=0.1まで有効) 0.1/10=0.01 (※-1-1=-2なので10^-2=0.01まで有効) 0.01/0.1=0.1 (※-2-(-1)=-1なので10^-1=0.1まで有効) でも、先の例があるので、油断はできない。
ちなみに、Javaならば、 BigDecimal a = new BigDecimal(1); BigDecimal b = new BigDecimal(10); a.divide(b);//・・・0.1が戻る。 と割り切れればいいけど、割り切れないと、こう: BigDecimal a = new BigDecimal(1); BigDecimal b = new BigDecimal(3); a.divide(b);//表現できない値なので、ArithmeticException例外が発生する。
こんなときは、 a.divide(b, 2, RoundingMode.FLOOR);//0.33 ってやれば、小数点以下第3位を切り捨てて、第2位までが有効になる。
丸めタイプの指定は http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/java/math/RoundingMode.html この辺が詳しい。
以上、明日への備忘録。 |
03:55, Tuesday, Apr 20, 2010 ¦ 固定リンク
¦ 携帯
■コメント
■コメントを書く
※コメントの受け付けは終了しました
|
|