やる気がストロングZERO

やる気のストロングスタイル

Goでdecimalを使う時に考えたこと(floatの精度について)

Goで金額計算するのでdecimal型を探したが、基本ライブラリには無いようなのでどうしようか悩んでいた。

基本ライブラリにmath.bigパッケージがあり、Floatの精度を自由に決められるようなものがあったのでそれで行けるかと思ってたけど(不必要に外部ライブラリを使いたくなかった)考え方が根本から間違ってたのでまとめる。

(結局はdecimal型を提供してくれるライブラリを採用した。 decimal - GoDoc

参考:
料率計算における小数の扱いについて
なぜBigDecimalを使わなければならないのか | Java好き

float型で精度をたっぷり用意すれば金額計算でも使える?

「float型は誤差が出る」というのは知ってたので金額計算は使えないという認識はあった。

でも基本ライブラリにdecimalが無いのでどうしようか悩んでる時、ふと
今回扱うのはせいぜい少数第2位くらいまでなので、じゃあ少数第10位くらいまでの「精度」を用意してやれば使えるんじゃね?

みたいに思った。

漠然とこんなイメージ

0.1 + 0.7 = 0.8000000012
で、少数第二位くらいで切り捨てればいいじゃん
答え:0.80

情報系の専門学校や大学を出てたら当然の話なのかもですけど、、
すみません。完全に間違ってました。

floatの精度とは?

floatでは10進数で表せない数値が存在する。
例えば0.01は2進数だと循環してしまうので、メモリに保持するにはどこかで切り捨てなければならない。
このとき「どこまでデータを保持して切り捨てるか」がfloatで言うところの精度となる。

だからどれだけ精度をあげても0.01は決して0.01としては扱えず、ごくわずかの誤差が入ってくる。

色んな所で切り捨てが起こり誤差は膨らむ一方。正確な計算は出来ない。

f1 = 0.1
f2 = 0.7
f3 = f1 + f2

f3 == 0.8 // false 

人の感覚的にはf3は0.8なのでtrueになるべきだが、 0.1と0.7の定数定義の時点で「切り捨て」が発生し、f3には2度の「切り捨て」が含まれた結果になっている。 なので0.8とは「切り捨て」の事情が異なる分だけ差が出ているのでfalseとなる。

最初の思惑「少数第二位くらいで切り捨てればいいじゃん」をやろうとすると、常にどこで「切り捨て」が発生するのかを意識して自分で切り捨て(もしくは切り上げ)を行い続けなくてはならず(他にも何を考慮すればいいのかさえよくわからない)完全に間違った策であることを理解した。

そもそもfloat型ってのは大きい数値を扱えるかわりに精度に関しては犠牲にするっていう型なのでキッチリした計算に使ってはいけないものだと言うことでした。

decimalはどうやって計算してんの?

0.01という数値は1(int)と小数点位置-2(int)をデータとして持って扱うので誤差が出ない。