[#42503] floatの値がずれる — Sato Hiroshi <hirocy.f01@...>

hirocyと申します.

33 messages 2006/07/04
[#42504] Re: floatの値がずれる — rubikitch <rubikitch@...> 2006/07/04

From: Sato Hiroshi <hirocy.f01@plala.to>

[#42505] Re: floatの値がずれる — Sato Hiroshi <hirocy.f01@...> 2006/07/04

hirocyです.るびきちさん,ありがとうございます.

[#42569] JVN、スクリプト言語「Ruby」の2件の脆弱性情報を公表 — Takahiro Kambe <taca@...>

こんばんは。

19 messages 2006/07/11
[#42570] Re: JVN、スクリプト言語「Ruby」の2件の脆弱性情報を公表 — Yukihiro Matsumoto <matz@...> 2006/07/11

まつもと ゆきひろです

[#42572] Re: JVN、スクリプト言語「Ruby」の2件の脆弱性情報を公表 — Takahiro Kambe <taca@...> 2006/07/11

In message <1152619872.835566.21152.nullmailer@x31.priv.netlab.jp>

[#42575] Re: JVN、スクリプト言語「Ruby」の2件の脆弱性情報を公表 — Yukihiro Matsumoto <matz@...> 2006/07/11

まつもと ゆきひろです

[ruby-list:42510] Float/String#to_r /Re: floatの値がずれる

From: Masaaki Sakano <mas@...>
Date: 2006-07-04 16:50:56 UTC
List: ruby-list #42510
坂野 正明です。

At Tue, 4 Jul 2006 18:51:38 +0900,
rubikitch <rubikitch@ruby-lang.org> wrote:
> From: Sato Hiroshi <hirocy.f01@plala.to>
> Subject: [ruby-list:42503] floatの値がずれる
> Date: Tue, 4 Jul 2006 18:38:19 +0900
> > floatをどんどん加算していくと,ときどき期待する値からずれることがあります.

> これはruby以前の問題で、浮動小数点数の仕様(丸め誤差)です。
> 10進数で1/3が無限小数になるように、2進数で0.001は無限小数になるのです。

今までにこの ruby-list でも何度も繰り返されてきた FAQ、つまり
落とし穴ですね。たとえば、[ruby-list:41514] 辺りのスレッドとか。

一般に、
>> 2. 繰り返される驚き: 改善の目印になる。
  (田中哲「使いやすいライブラリ API デザイン」)
  http://jp.rubyist.net/magazine/?RubyKaigi2006-0610-3
という話もあります。
僕も、(実行コストでなく)思考コストを下げるために、ここは(Ruby の
仕様を)考える価値あるところだと思います。
# 拙見は、[ruby-list:39484] でまとめたことがあります。


さて、hirocy さんの質問に関しては、Float を使わない、というのが、
最も一般的で明快な解だと思います。
# 原さん御提案のように、Float の "+" の定義を書き換えるのは
# 立派な一案ですが、ちょっと過激かも? (^^;  影響範囲広そうで。
# もっとも、僕の持論は Floatのリテラルを追放しませう、だから、
# 人様のことを言えたものではないかも知れませんけど。

具体的には、Rational に変換するメソッド to_r (下記参照) を使って、
 x = [0]	## 0.0 (=Float) ではない。
 100.times { x << x[-1] + 0.001.to_r }
 p x
とすれば、丸め誤差の入る余地はなく、解決できます。

Float#to_r は、昔、僕が投稿しました。
 [ruby-list:35966]
 http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/35966
でも、当時は 1.6 対応だったので、Ruby 1.8 対応にしたものを、
以下に添付します。String#to_r も含んでいます。
# String#to_r は、あくつさんが、[ruby-list:39574] で要望を出されて
# いましたね。

坂野 正明


# $Id: to_r.rb,v 1.5 2006/07/04 15:43:58 sakano Exp $

=begin

= Float#to_r({:precision=>15})
= String#to_r({:divide=>false, :precision=>15})
= Integer#to_r({:precision=>15})
= Rational#to_r({:precision=>15})

All the to_r methods take the optional one argument.
It can be a simple integer number, meaning the precision,
or a hash as indicated.

Float#to_r({:precision=>15}) returns the instance of Rational,
converting the Float number with the given precision (Integer,
optional).  The default precision is the maximum float digit
number that the system can handle (Float::DIG) (or is 15 in Ruby 1.6).

String#to_r({:divide=>false}) is similar.  The precision is
infinite, though, unless the precision is also specified.
As the optional 1st argument, either
 {:divide=>(true|false), :precision=>Integer},
 or simply (true|false) (meaning :divide),
 or a simple integer number (meaning :precision)
is accepted.
If the argument is {:divide=>true}, the format like "55/13",
i.e., that including a single '/', is accepted (Default: false).

Integer#to_r and Rational#to_r are similar to the other to_r().
If the precision is specified, the rounded number with the given
precision is returned (nb. it could be identical to the value where
the precision is not specified), otherwise the precision is infinite.
If the given precision exceeds the maximum precision that the system
can handle for a float, then simply the infinite precision is applied
(nb., in Ruby1.6, it tries to apply the given precision, but the
true precision of the returned value is not guaranteed.).

: Examples

	Math::PI.to_r({:precision=>2})   # => Rational(31, 10) (=3.1)
	Math::PI.to_r({:precision=>5})   # => Rational(3927, 1250) (=3.1416)
	"3.1416".to_r                    # => Rational(3927, 1250) (=3.1416)
	"31/10".to_r({:divide=>true})   # => Rational(31, 10) (=3.1)

: Acknowledgement

    Many thanks to Nobu Nakada, who has kindly improved the original code!

: References

	Subject: [ruby-list:35966] Re: Float#to_r (Re: 9.2 ...)
	From: nobu.nakada@nifty.ne.jp
	Message-Id: <200209180120.g8I1Krv01082@sharui.nakada.kanuma.tochigi.jp>
	http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/35966

	Subject: [ruby-list:35956] Re: Float#to_r (Re: 9.2 ...)
	From: mas@star.le.ac.uk (Masaaki Sakano)
	Message-Id: <86vg54wflu.wl@dhcp.star.le.ac.uk>
	http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/35956

=end


require "rational"

class Float
  begin
    @@def_precision_to_r=Float::DIG
  rescue NameError		## Ruby 1.6 (Float::DIG is not defined)
    @@def_precision_to_r=15		## Integer (>0)
  end

  def to_r(prec={:precision=>@@def_precision_to_r})
    ## Input: Precision(Hash or Number)
    ## Return: Rational

    if defined? prec.key?
      begin
        ofs = (prec[:precision] || prec['precision']).round
      rescue NameError			## if (prec['precision']).nil?
        ofs = @@def_precision_to_r
      end
    else
      ## Form like to_r(5) is accepted.
      ofs = prec.round
    end
    ofs -= Math.log10(self.abs).floor + 1
    ## i.e.,   ofs = ofs(=prec.round) - Math.log10(self.abs).floor - 1

    num = (self * 10.0 ** ofs).round
    if ofs > 0
      Rational.reduce(num, 10.power!(ofs))
    else
      Rational.new!(num * 10.power!(-ofs), 1)
    end
  end
end


class String

  ### For Ruby 1.6 or before
  if ! defined?("".lstrip)
    def lstrip
      sub(/^[ \t\r\n\f\v]*/, '')
    end
  end
  if ! defined?("".rstrip)
    def rstrip
      sub(/[ \t\r\n\f\v]*$/, '')
    end
  end

  def to_r(hsallowdivide={'divide'=>false})
    ## Input: {"divide"=>true} if you want to allow the format 'A/B' (Default: false)
    ##      : Simple true|false is accepted, too.
    ## Return: nil if /^[\s\n]*$/, or TypeError if non-number, else Rational

    if hsallowdivide == true
      allowdivide=true
      hsallowdivide={'divide'=>true}
    elsif defined?(hsallowdivide.key?)
      allowdivide = hsallowdivide['divide'] || hsallowdivide[:divide]	## possibly nil
    else
      allowdivide=false
      if hsallowdivide == false
	hsallowdivide={'divide'=>false}
      end
    end

    selfstripped=self.strip
    if /[\n\r]/ =~ selfstripped
      raise TypeError, "(#{selfstripped}) does not look like a number."
    elsif selfstripped.empty?
      return nil
    end

    if allowdivide
      matched=%r@^([^/]+)(?:(/)\s*(\S+))?$@.match(selfstripped)
      if matched.nil?
        raise TypeError, "(#{selfstripped}) does not look like a number."
      elsif matched[2].nil?
        denom=1
      else
        denom=matched[3].__to_r_core
      end
      (matched[1].rstrip.__to_r_core/denom).to_r(hsallowdivide)
    else
      (selfstripped.__to_r_core).to_r(hsallowdivide)	# to_r(): for precision
    end
  end


  def __to_r_core
    ## self should be already "stripped".
    # s=self.strip

    ## Accept -0009 but -0009.5
    #if (/^([+\-]?)0+(\d*)$/ =~ self)	# NOT accept "_"
    if (/^([+\-]?)0+([0-9_]*)$/ =~ self)	# Accept "_"
      self.to_i.to_r

    ## Style in Ruby 1.6 or before
    # if (/^([+\-]?((0|[1-9]\d*)?\.[0-9]+|(0|[1-9]\d*)\.?)([Ee][+\-]?\d+)?)$/ =~ self)
    # elsif (/^(([+\-]?(?:0|[1-9]\d*))(?:\.(\d+))?)(?:[Ee]([+\-]?)(\d+))?$/ =~ self)
      ## ## Note that the string including "_" is NOT regarded as a number.

    elsif (/^(([+\-]?(?:0|[1-9][0-9_]*))(?:\.(\d[0-9_]*))?)(?:[Ee]([+\-]?)(\d[0-9_]*))?$/ =~ self)
      # The string including "_" IS regarded as a number.

      # pow = (10.power($5.size) rescue 1)
      if $5.nil?
        pow=1
      else
        pow = 10.power!($5.to_i)
      end

      if $3.nil?		## NOT \d.\d form.
        numer=$2.to_i
        denom=1
      else
        numer=($2+$3).to_i
        denom=10.power!($3.size)
      end

      if $4 == '-'
        Rational(numer,denom) / pow
      else	## $4 == '+' OR ''
        Rational(numer,denom) * pow
      end
    else
      raise TypeError, "(#{self}) does not look like a number."
    end
  end
end


class Integer
  def to_r(prec=nil)
    Rational(self, 1).to_r(prec)
  end
end


class Rational
  def to_r(prec=nil)
    ## Input: [precision]
    ##   If the precision is defined, the value closest within the precision
    ##   is returned.  If the precision is greater than the maximum
    ##   precision that the system can handle for a float, self
    ##   is simply returned, regardless of the input "precision".

    if prec.nil?
      self
    elsif defined? prec.key?
      precnow = (prec[:precision] || prec['precision'])
      if precnow.nil?
	self
      else
	begin
	  if precnow > Float::DIG
	    self
	  else
	    self.to_f.to_r(prec)
	  end
	rescue NameError	## Ruby 1.6 (Float::DIG is not defined)
	    self.to_f.to_r(prec)
	end
      end
    else
      ## Assuming the form like to_r(5).
      begin
	if prec > Float::DIG
	  self
	else
	  self.to_f.to_r(prec)
	end
      rescue NameError		## Ruby 1.6 (Float::DIG is not defined)
	  self.to_f.to_r(prec)
      end
    end
  end
end

In This Thread