こんにちは。ラクマでサーバーサイドエンジニアをやっているYuです。
最近、久しぶりの外出で日焼け止めを忘れて肌が赤くなりました。 みなさんは気をつけてください。
今回は、小数点を含む計算で発生する誤差と、Rubyでの対応方法についてお話したいと思います。
数字の計算って大事ですよね。1でもずれてたら大問題になってしまうこともあります。 ラクマでも手数料などお金の計算が行われていますので、正しく計算しなければいけません。
小数を含む計算をしてみよう
例として72000 * 0.086
を計算してみましょう。
72 000 x 0.086 ------- 432 000 5760 00 -------- 6192.000
小学生のころのように紙に書いて筆算してみると72000 * 0.086 = 6192
になりました。小数点が出ないぴったりの数になりました。
さて、この計算をrubyのrails consoleで実行してみました。
(以下ruby: v2.7.1
およびrails: v6.0.2.2
で確認しています)
irb(main):001:0> 72000 * 0.086 => 6191.999999999999
あれ?
ほとんど6192ですが、ぴったりにはなりません。 これを整数に変換してみると、
irb(main):002:0> (72000*0.086).to_i => 6191
正しい結果と比べて1ずれてしまいました。
どうして結果がずれてしまったの?
どうしてこのような結果になったのでしょうか? それを知るためには、コンピュータが小数をどう扱っているかを知る必要があります。
コンピュータが数字を扱うとき、2進数として扱っていることはよく知られていると思います。 (以下簡単な説明のため、小数の2進数表現について、やや正確性に欠けるところがあります。)
10 (10進数) -> 1010 (2進数) (10=2+8なので) 0.25 (10進数) -> 0.01 (2進数) (0.25 = 1/4なので、1を右に2つシフトする)
さて、この方法で小数を表現することにすると、小数を表現するためには以下のステップが必要とわかります。
1. ある整数を用意して 2. それを何回か右にシフトして 3. 目的の小数を表現する
上記の0.25
の場合は、以下のようになります。
1. 1を用意して(1) 2. それを右に2つシフトして(0.01) 3. 0.25 = 1/4が表現できる
同じように、2.5
を表現する場合は、
1. 10を用意して(1010) 2. それを右に2つシフトして(10.10) 3. 2.5 = 10/4が表現できる
そして、これを一般化すると、ある数x
を2進数で正確に表現するためには、ある整数n
とk
が存在して
x = k / (2 ^ n)
と表現できることが必要だということがわかります。
逆にいうと、このように表現できない小数は、2進数で正確に表現できないのです。
0.086
には、どんなに探してもこのような表現方法は見つかりません!
なので、0.086
ではなくてそれに一番近い小数を代わりに使っていて、ずれてしまうのです。
どうすればずれないの?
どうすればずれないのでしょうか? 小数のまま計算してしまうとずれてしまうので、整数の計算(有理数の計算)に帰着させることが必要になります。
(有理数
というのは、(整数)/(整数)
の形で表現することができる数のこと。
0.5
は1/2
と表せるから有理数だが、π=3.1415...
にはそのように表す方法はないから有理数じゃない。)
そこでRationalクラスというものを使ってみましょう。
0.086 = 86/1000
ということをふまえると、以下のように計算ができます。
irb(main):003:0> (72000 * Rational(86, 1000)).to_i => 6192
整数だけで計算を完結させることができるので、正しい値を表示することができました!
まとめ
このように、小数を含む計算を行うときには誤差が生じる可能性があります。 誤差が許容できない場合は、Rationalクラスを使って整数の計算に帰着させるといった対応が必要になります。