From: "NARUSE, Yui" Date: 2010-03-28T14:47:50+09:00 Subject: [ruby-dev:40799] Re: [Feature #2969] String#to_f が -h.hhh±pd を解釈できるように (2010/03/27 20:56), Tadayoshi Funaba wrote: >> つまり、ふなばさんは 16 進よりも 2 進や 8 進形式が好みであるところ、 >> 16 進がそれらを差し置いて入るのは納得できないという事でしょうか。 > > 統一性とバランスの問題です。 何の統一性とバランスでしょう。 String#to_i との統一性とバランス、でしょうか。 >> わたしの主張は、以下のようなものです。 >> (1) -h.hhh±pd 形式は、符号・仮数・指数という浮動小数点数をそのまま表しているため、 >> 浮動小数点の内容を知るのに有用である >> (2) -h.hhh±pd 形式は、C99 の浮動小数点数リテラルの一形式であり、 >> その形式は文法に、出力は printf に、解釈は strtod に記述されている >> (3) 有用かつ安定した形式なので Ruby でも扱えるべきだ > > ruby は C じゃないんで、直ちに ruby で採用しなければということにはらな > いでしょう。しかし、ruby でつかえてもいいと思います。で、一応 scanf と > いう対案を出しています。しかし、これまで strtod がどうのという話を繰り > 返し、無視されている状態です。 strtod(3) の話は、16進整数表現の解釈はできないのかとの問いに対して、 strtod(3) の範囲ならば矛盾無く拡張できると返したのが最初です。 また、printf の反対なのだから scanf で対応するべきという主張に対しては、 16進浮動小数点数形式も浮動小数点数形式なので printf との対応にこだわらず、 String#to_f でも扱えていいと思うと返しています。 なお、先に scanf 側にも実装するべきとの主張ならばそうかもしれません。 と、思って lib/scanf.rb を見ると……、 def extract_float(s); s.to_f if s &&! skip; end えーっと、lib/scanf.rb で使うというユースケースを String#to_f に 追加していいですか。 とりあえず Ruby で実装するなら以下の通りでしょうか。 diff --git a/lib/scanf.rb b/lib/scanf.rb index ffc0d90..dd6ba6c 100644 --- a/lib/scanf.rb +++ b/lib/scanf.rb @@ -112,7 +112,7 @@ and tests/scanftests.rb for examples.) [x,X] Matches an optionally signed hexadecimal integer, -[f,g,e,E] +[a,e,f,g,A,E,F,G] Matches an optionally signed floating-point number. [s] @@ -309,7 +309,22 @@ module Scanf def skip; /^\s*%\*/.match(@spec_string); end - def extract_float(s); s.to_f if s &&! skip; end + def extract_float(s) + return nil unless s &&! skip + if /\A(?[-+]?)0[xX](?\.\h+|\h+(?:\.\h*)?)[pP](?[-+]\d+)/ =~ s + f1, f2 = frac.split('.') + f = f1.hex + if f2 + len = f2.length + if len > 0 + f += f2.hex / (16.0 ** len) + end + end + (sign == ?- ? -1 : 1) * Math.ldexp(f, exp.to_i) + else + s.to_f + end + end def extract_decimal(s); s.to_i if s &&! skip; end def extract_hex(s); s.hex if s &&! skip; end def extract_octal(s); s.oct if s &&! skip; end @@ -409,12 +424,12 @@ module Scanf [ "([-+][0-7]{1,#{$1.to_i-1}}|[0-7]{1,#{$1}})", :extract_octal ] # %f - when /%\*?[efgEFG]/ - [ '([-+]?(?:\d+(?![\d.])|\d*\.\d*(?:[eE][-+]?\d+)?))', :extract_float ] + when /%\*?[aefgAEFG]/ + [ '([-+]?(?:0[xX](?:\.\h+|\h+(?:\.\h*)?)[pP][-+]\d+|\d+(?![\d.])|\d*\.\d*(?:[eE][-+]?\d+)?))', :extract_float ] # %5f - when /%\*?(\d+)[efgEFG]/ - [ '(?=[-+]?(?:\d+(?![\d.])|\d*\.\d*(?:[eE][-+]?\d+)?))' + + when /%\*?(\d+)[aefgAEFG]/ + [ '(?=[-+]?(?:0[xX](?:\.\h+|\h+(?:\.\h*)?)[pP][-+]\d+|\d+(?![\d.])|\d*\.\d*(?:[eE][-+]?\d+)?))' + "(\\S{1,#{$1}})", :extract_float ] # %5s @@ -491,7 +506,7 @@ module Scanf attr_reader :string_left, :last_spec_tried, :last_match_tried, :matched_count, :space - SPECIFIERS = 'diuXxofFeEgGsc' + SPECIFIERS = 'diuXxofFeEgGscaA' REGEX = / # possible space, followed by... (?:\s* diff --git a/test/scanf/test_scanf.rb b/test/scanf/test_scanf.rb index 2ec4e54..169ffe6 100644 --- a/test/scanf/test_scanf.rb +++ b/test/scanf/test_scanf.rb @@ -276,6 +276,8 @@ module ScanfTests [ "%g", "+3.25", [3.25] ], [ "%G", "+3.25e2", [325.0] ], [ "%f", "3.z", [3.0] ], + [ "%a", "0X1P+10", [1024.0] ], + [ "%A", "0x1.deadbeefp+99", [1.1851510441583988e+30] ], # Testing embedded matches including literal '[' behavior [",%d,%f", ",10,1.1", [10,1.1] ], -- NARUSE, Yui