Java
JavaScript
プログラミング

プログラミングで小数はヤバイんだぜ?という話。

プログラミングで小数を扱う時は細心の注意が必要です。
この春からお仕事でプログラムを書くことを始めた方もいらっしゃると思うので、注意喚起の目的で、できるだけわかりやすく、理由込みで説明してみます。
知ってる人には常識ですが、知らない人には大きな落とし穴です。ヤバさを知らなそうな人がいたら、教えてあげてください。

なぜ、小数はヤバイのか。

結論から言うと、実世界が10進数で計算するのが殆どになのに、コンピュータが2進数で数を扱うためです。基数を変換しつつ小数を計算するのがヤバイのです。

では、なぜ基数を変換しつつ小数を扱うとヤバイのでしょうか?
まずは、3進数と10進数とで説明します。

3進数の0.1って?

3進数の0.1とは、3回足すと1になる数字です。0.1+0.1=0.2。0.2+0.1は繰り上がって1ですね。
分数で表すと1/3のことです。
では、10進数において、1/3とは何でしょうか?3回足すと1になる数字。それは
0.3333333333333333333・・・・・・
無限小数ですね。
3進数では有限小数だった0.1が、10進数では無限小数になります。
そして、コンピュータのリソースは有限なので、無限小数は保持できません。64bit 浮動小数点型だと、保持できる小数は最大16桁程度です。
そのために、コンピュータで小数を持つ時は近似値で持つことがあります。
もし、コンピュータが10進数で数字を持っていたとしたら、1/3 は 0.3333333333333333 として保持されることになるでしょう。これがヤバイのです。

2進数で表現できない値。

基数によって表現できる小数が違うことをを1/3を例に記しました。
では、2進数で無限小数になってしまう10進数とは何でしょう?
たとえば、10進数の0.2は、2進数では表現できません。
0.001100110011・・・・
と無限小数になります。
なぜ、0.001100110011・・・・になるかはtmt's mathemaTeX page!のPDFが詳しく説明してくれています。
ので、10進数の0.2はコンピュータ(の通常の浮動小数点型変数)では正確に持つことはできません。近似値を持つことになってしまいます。

計算誤差が起きる。

このことで、小数をプログラミングで扱うと問題がおきます。

const num = 0.2 + 0.1;
console.log("0.2 + 0.1 = " + num);
> 0.2 + 0.1 = 0.30000000000000004

0.3のはずが、0.00000000000000004 増えてしまってますね。これが問題なのです。
従量料金計算などで小数を使った場合、誤請求が発生してしまうかもしれません。恐ろしい。

ではどうするか?

通常の変数で小数を扱うのではなく、ライブラリを通して計算するようにしましょう。
JavaScriptでは、BigDecimal.jsbignumber.jsを使いましょう。
Java では標準ライブラリのBigDecimalクラスを使って計算しましょう。
他の言語でも、だいたい「BigDecimal」や「decimal」のような名前のライブラリがあります。

結論

小数を正確に計算したい時は、ライブラリを通して計算するようにしましょう。
ただ、描画の高さ計算等、そんなに正確に計算しなくてもいい時は、ライブラリではなく、素の変数で計算したほうがいいかもしれません。ライブラリは正確に計算するために、速度を犠牲にしていることがあるためです。

以上です。