有理数しか使っていないのに誤差があり、なんでだろうと思って調べた。
直接の原因は、"%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 が特別だという印象になった。