kumamotone/gfdm-skill

スキル値の計算がおかしい(Float(92.74).to_d.floor(2).to_f は92.73 になる)

Closed this issue · 2 comments

スキル値の計算は以下の式でやっています.

https://github.com/kumamotone/gfdm-skill/blob/master/app/helpers/application_helper.rb#L88

((rate * level * 20) * 0.01).to_d.floor(2).to_f

このto_d.floor(2).to_fというのは,Float に floor がない ruby において,
強引に小数点以下2位にて切り捨て処理をしてしまう処理で,以下の記事を参考にしています.

http://qiita.com/ryoff/items/eb2e8242561a105eb940

以上の記事でも言及されていることですが,
to_d でBigDecimalに変換しても,その前にFloatの変数に何か代入した時点で,
エラーがおこらなくとも誤差で値が意図しないものになっていることがあります.

実際,rate=92.74,level=5.00のとき,以下のようにおかしくなります.

irb(main):014:0> rate = 92.74
=> 92.74
irb(main):015:0> level = 5.00
=> 5.0
irb(main):016:0> ((rate * level * 20) * 0.01).to_d.floor(2).to_f
=> 92.73

調べてみると,掛け算の結果おかしくなっているというよりか,92.74は,
Float として宣言した瞬間 92.7399999... に近い値になっているように見えます.

irb(main):001:0> require 'bigdecimal/util'
=> true
irb(main):002:0> require 'bigdecimal'
=> true
irb(main):008:0> 92.72.to_d.floor(2).to_f
=> 92.72
irb(main):010:0> 92.73.to_d.floor(2).to_f
=> 92.73
irb(main):009:0> 92.74.to_d.floor(2).to_f
=> 92.73
irb(main):011:0> 92.75.to_d.floor(2).to_f
=> 92.75

これを解消する一つの方法のひとつは
to_d する前に to_s してしまうことのようです.

irb(main):013:0> 92.74.to_s.to_d.floor(2).to_f
=> 92.74

というわけで,以下のように計算後に to_s するように変更を加えればよさそうです.

irb(main):018:0> ((rate * level * 20) * 0.01).to_d.floor(2).to_f
=> 92.73
irb(main):019:0> ((rate * level * 20) * 0.01).to_s.to_d.floor(2).to_f
=> 92.74

おわり

((92.74 * 5.00 * 20) * 0.01) は,.to_sすると92.74になるっぽいですし,
他のパターンでもto_sすると小数点2点レベルでは正しい,ということがあり,一見解決っぽかったですが,
((69.75 * 9.20 * 20) * 0.01) は,128.3399999...97 になるらしく,
やはりこの後BigDecimalに戻してfloor(2)を掛けても,128.33になってしまい,
正しく128.34になりません.

当然といえば当然ですが,正確な値が欲しければ,
掛け算の時点でBigDecimalを使え,ということですね……いい教訓になりました…
近いうちに修正します.

227b214

ちょっと状況忘れましたが以上のコミットで直ってるんじゃないかと思います.