有理数しか使っていないのに誤差があり、なんでだろうと思って調べた。
直接の原因は、"%g" による書式化。
# ruby 2.2.0
"%.20g" % 1.001r #=> "1.0009999999999998899"
しかし、%f は大丈夫。
# ruby 2.2.0
"%.20f" % 1.001r #=> "1.00100000000000000000"
かと思ったら、1.9 だとそうでもない。
# ruby 1.9.3-p551
"%.20f" % "1.001".to_r #=> "1.00099999999999988987"
という状況に不安を感じ、いろいろ実験してみた:
調査結果
ruby 2.2.0p0
||Rational|Float|
|:--|:--|:--|:--|:--|
|inspect|(1001/1000)|1.001|
|to_s|1001/1000|1.001|
|%f|1.001000|1.001000|
|%.20f|1.00100000000000000000|1.00099999999999988987|
|%g|1.001|1.001|
|%.20g|1.0009999999999998899|1.0009999999999998899|
|%e|1.001000e+00|1.001000e+00|
|%.20e|1.00099999999999988987e+00|1.00099999999999988987e+00|
ruby 2.1.5p273
||Rational|Float|
|:--|:--|:--|:--|:--|
|inspect|(1001/1000)|1.001|
|to_s|1001/1000|1.001|
|%f|1.001000|1.001000|
|%.20f|1.00099999999999988987|1.00099999999999988987|
|%g|1.001|1.001|
|%.20g|1.0009999999999998899|1.0009999999999998899|
|%e|1.001000e+00|1.001000e+00|
|%.20e|1.00099999999999988987e+00|1.00099999999999988987e+00|
ruby 2.0.0p598
||Rational|Float|
|:--|:--|:--|:--|:--|
|inspect|(1001/1000)|1.001|
|to_s|1001/1000|1.001|
|%f|1.001000|1.001000|
|%.20f|1.00099999999999988987|1.00099999999999988987|
|%g|1.001|1.001|
|%.20g|1.0009999999999998899|1.0009999999999998899|
|%e|1.001000e+00|1.001000e+00|
|%.20e|1.00099999999999988987e+00|1.00099999999999988987e+00|
ruby 1.9.3p551
||Rational|Float|
|:--|:--|:--|:--|:--|
|inspect|(1001/1000)|1.001|
|to_s|1001/1000|1.001|
|%f|1.001000|1.001000|
|%.20f|1.00099999999999988987|1.00099999999999988987|
|%g|1.001|1.001|
|%.20g|1.0009999999999998899|1.0009999999999998899|
|%e|1.001000e+00|1.001000e+00|
|%.20e|1.00099999999999988987e+00|1.00099999999999988987e+00|
jruby 1.7.18 (1.9.3p551)
||Rational|Float|
|:--|:--|:--|:--|:--|
|inspect|(1001/1000)|1.001|
|to_s|1001/1000|1.001|
|%f|1.001000|1.001000|
|%.20f|1.00100000000000000000|1.00100000000000000000|
|%g|1.001|1.001|
|%.20g|1.001|1.001|
|%e|1.001000e+00|1.001000e+00|
|%.20e|1.00100000000000000000e+00|1.00100000000000000000e+00|
調査のためソースコード
vals=[ "1.001".to_r, 1.001]
forms=[:inspect, :to_s] + %w( %f %.20f %g %.20g %e %.20e)
puts (["",""]+vals.map( &:class)+[""]).join("|")
puts( "|:--"*5+"|")
forms.map{ |f|
s=["", f]+vals.map{ |v|
case f
when Symbol
v.send(f)
else
f % v
end
}+[""]
puts s.join("|")
}
まとめ
-
"%.20f" % 1.001r
の件は、 2.1.5 まではなんかおかしかったけど、2.2.0 で治ったらしい。 -
"%.20g" % 1.001r
と"%.20e" % 1.001r
は 2.2.0 でも治ってない。 - jruby の
"%.20f"%1.001
の出力は誠実じゃないと思う。
他の言語で "%.20f" % 1.001
がどうなるか。
1.001 の浮動小数点表現はぴったり 1.001 ではないので、"%.20f"
のようなことをしたときに 1.00100000000000000000
のような出力をするのは誠実ではないように思う。
他の言語がどうしているのか調べてみた。
(1..3).map{ |x| "%.20f" % (1.001*x ) }
(1..3).collect{sprintf("%.20f",it*(double)1.001)}
["%.20f" % (x*1.001) for x in [1,2,3]]
# include <stdio.h>
// clang -std=c99 -Wall
// Apple LLVM version 6.0 (clang-600.0.56)
int main()
{for( int i=1 ; i<=3 ; ++i ){printf( "%.20f ", 1.001*i );}}
の結果は、以下のようになる:
処理系 | 結果 |
---|---|
ruby2.2.0 | ["1.00099999999999988987", "2.00199999999999977973", "3.00299999999999966960"] |
jruby 1.7.18 (1.9.3p551) | ["1.00100000000000000000", "2.00200000000000000000", "3.00299999999999970000"] |
groovy2.3.9 | [1.00100000000000000000, 2.00200000000000000000, 3.00299999999999970000] |
Python 3.4.2 | ['1.00099999999999988987', '2.00199999999999977973', '3.00299999999999966960'] |
C99(clang) | 1.00099999999999988987 2.00199999999999977973 3.00299999999999966960 |
というわけで、JVM が特別だという印象になった。