From: Masaaki Sakano Date: 2006-07-05T01:50:56+09:00 Subject: [ruby-list:42510] Float/String#to_r /Re: floatの値がずれる --Multipart_Tue_Jul__4_17:50:52_2006-1 Content-Type: text/plain; charset=ISO-2022-JP 坂野 正明です。 At Tue, 4 Jul 2006 18:51:38 +0900, rubikitch wrote: > From: Sato Hiroshi > 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] で要望を出されて # いましたね。 坂野 正明 --Multipart_Tue_Jul__4_17:50:52_2006-1 Content-Type: text/plain; charset=US-ASCII # $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 --Multipart_Tue_Jul__4_17:50:52_2006-1--