LoginSignup
3
3

More than 5 years have passed since last update.

('%.20g' % 1.001r).to_r != 1.001r

Last updated at Posted at 2015-01-30

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

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3