[Ruby 1.9 - Bug #4591][Open] (1.5.2).max #=> 1 (Range#max)

Issue #4591 has been reported by Masaya T…


Bug #4591: (1.5…2).max #=> 1 (Range#max)

Author: Masaya T.
Status: Open
Priority: Normal
Assignee:
Category: core
Target version:
ruby -v: ruby 1.9.3dev (2011-04-20 trunk 31311) [i386-mswin32_100]

現在、(1.5…2).maxが1になります。
beginより小さい値が返ってくるのは違和感があります。

終端を含まないRangeについて、endがIntegerである時にend-1を返していますが、
それはbeginもIntegerである事が想定されてると思います。
結局 beginがIntegerじゃないときは、endがIntegerでない時と同様に
maxが定義できなさそうなので、やはり同様にErrorにした方が親切かと思います。

以下のパッチを入れていいですか?

Index: range.c

— range.c (リビジョン 31313)
+++ range.c (作業コピー)
@@ -670,6 +670,9 @@
rb_raise(rb_eTypeError, “cannot exclude non Integer end
value”);

        }
        if (c == 0) return Qnil;
  •       if (!FIXNUM_P(b) && !rb_obj_is_kind_of(b,rb_cInteger)) {
    
  •           rb_raise(rb_eTypeError, "cannot exclude end value with 
    

non Integer begin value");

  •       }
          if (FIXNUM_P(e)) {
              return LONG2NUM(FIX2LONG(e) - 1);
          }
    

Index: test/ruby/test_range.rb

— test/ruby/test_range.rb (リビジョン 31313)
+++ test/ruby/test_range.rb (作業コピー)
@@ -68,6 +68,8 @@
assert_equal(2.0, (1.0…2.0).max)
assert_equal(nil, (2.0…1.0).max)
assert_raise(TypeError) { (1.0…2.0).max }

  • assert_raise(TypeError) { (1…1.5).max }

  • assert_raise(TypeError) { (1.5…2).max }

    assert_equal(-0x80000002, ((-0x80000002)…(-0x80000001)).max)

Assignee:
maxが定義できなさそうなので、やはり同様にErrorにした方が親切かと思います。
わたしRubyをまったく知らないんですけど、rangeに対するmaxってようするに

  1. rangeの範囲に収まる整数値をすべて取り出す
  2. その集合に対して、最大の数値を取り出す

という操作をしているわけですよね。ここで、(1)で整数値が1つも取り出せなかった
(今回のケース)のときにtype errorになるのは納得できるのですが、そうじゃないときも
errorにするのが親切なんですかね?

end-1がbeginより小さかったらエラー。というロジックだと困ります?endが
非整数値のときの扱いにも同じ疑問があるのですが。

むらたです。

test_range.rb の test_max を見てみました。

assert_equal(2, (1…2).max)
assert_equal(nil, (2…1).max)
assert_equal(1, (1…2).max)

assert_equal(2.0, (1.0…2.0).max)
assert_equal(nil, (2.0…1.0).max)
assert_raise(TypeError) { (1.0…2.0).max }

assert_equal(-0x80000002, ((-0x80000002)…(-0x80000001)).max)

assert_equal(0, (0…0).max)
assert_equal(nil, (0…0).max)

こうしてみると、以下の仕様に一貫性がないように感じます。

assert_equal(nil, (2…1).max)
assert_equal(nil, (2.0…1.0).max)
assert_raise(TypeError) { (1.0…2.0).max }

次の、tarui さんによる追加は一貫性が無い方に分類できると思います。

assert_raise(TypeError) { (1.5…2).max }
assert_raise(TypeError) { (1…1.5).max }

Range#max の明確な定義が必要ですよね。
既に決まっているなら良いのですが、少なくとも RDoc には

Returns the maximum value in rng. The second uses
the block to compare values. Returns nil if the first
value in range is larger than the last value.

このようにしか書かれていませんから、明確に定義されてるようには思えません。

Range は Enumerable を include しているので Range#max は Enumerable#max なんだと思います。
そうすると、Range#each が下限が succ をサポートする事を要請しているので、
下限が Float だったら有無を言わさず TypeError になるべきです。従って、

assert_equal(nil, (2.0…1.0).max)

このアサーションは間違っていることになります。

また、上限が succ をサポートしている必要は無いようですから、
tarui さんが追加しようとしている

assert_raise(TypeError) { (1…1.5).max }

これも間違っています。TypeError ではなく 1 を返すはずです。

On 2011年4月21日木曜日 at 8:55, KOSAKI Motohiro wrote:

わたしRubyをまったく知らないんですけど、rangeに対するmaxってようするに

  1. rangeの範囲に収まる整数値をすべて取り出す
  2. その集合に対して、最大の数値を取り出す

数値範囲の最大値を求めるだけなら下限が succ をサポートしている必要はないので、
小崎さんが言うように数値範囲を適切に扱いたくなるのはとても共感できます。
しかし、数値範囲としての振る舞いを考えようとすると範囲の下限が Float や BigDecimal
のような inexact 数の場合に難しくなります。

Float と BigDecimal は、それ自身が有限の広がりを持っているので、
範囲の境界がぼやけてしまいます。ですから

assert_raise(TypeError) { (1.0…2.0).max }

この振る舞いが正しいかどうか決めるためには、
整数の 1 (つまり 1 以外の何者でもない) が 1.0 … 2.0 の範囲に含まれるかどうかを
定義する必要があります。

Float の 1.0 は、C の double が IEEE754倍精度の場合、半開区間 [1, 1 + 1/2^51) に含まれる
すべての実数を代表する数なので、範囲 1.0 … 2.0 に整数 1 が含まれるかどうかは
基本的には文脈依存でしか判断できない問題になります。

リテラルの 1.0 は基本的に 1 と等しいんでしょうけど、

何らかの計算過程で得られた 1.0 が 1 と等しいかどうかは値だけでは判断できませんから。

$B$=$b$=$b!"(Bbegin$B$H(Bend$B$N%*%V%8%’%/%H5Z$S(BRange$B$N%a%=%C%I$K$h$C$F!"(B
$BN%;6CM$H$7$F07$&>l9g$H!"O"B3NL$H$7$F07$&>l9g$,:.$6$C$F$7$^$C$F$k$s$G$9$h$M!#(B

$BHs@0?tCM$N$H$-$N07$$$K$bF1$85?Ld$,$"$k$N$G$9$,!#(B
$BN%;6CM$r07$&$H;W$C$F$k$H$3$l$G$b$h$5$=$&$J$s$G$9$,!"(B
$B$=$&$9$k$H:#EY$O(B(1…1.5).max$B!!(B#=>1.5
$B$K$J$C$F$$$k$N$,(B1$B$K$7$J$$$H$$$1$J$/$J$C$F$7$^$$!"3d$HBgJQ99$K$J$j$=$&$G$9!#(B

$BC.2H$G$9!#(B

$B$`$i$?$G$9!#(B
snip

Range#max $B$NL@3N$JDj5A$,I,MW$G$9$h$M!#(B
snip

$B$3$3$G0l4S@-$,$J$$$H$5$l$F$$$k(B
assert_raise(TypeError) { (1.0…2.0).max }
$B$O(B#974$B$GF3F~$5$l$F$^$9!#(B
$B$G!"$=$N;~$N5DO@$r$_$k$H!"(B

matz> * $BK|?M$,G<F@$9$k(B ($BK|?M$8$c$J$/$FB?$/$,!"$G$b$$$$$1$I(B)
matz> * $B$+$DM-MQ$J(B
matz>$B:GBgCM$NM#0l$NDj5A$OB8:_$7$J$5$=$&$@!"$H$$$&$3$H$G$9(B

$B$H$$$&M}M3$G%(%i!<$K$7$F$$$^$9!#(B
$B$3$N$H$-$O(Bend$B$,(Bfloat$B$N;~$@$1$rLdBj$K$7$F$^$7$?$,!"(B
$B$@$C$?$i(Bbegin$B$,(Bfloat$B$N;~$bLdBj$G$7$g$&!#(B

$B:#2s$N(Bbug$BJs9p$O$3$N;~$NJQ99$KDI=>$7$FJQ99$9$Y$-$@$C$?$N$r(B
$BJQ99$7$F$J$+$C$?$s$G$O$J$$$+$H$$$&%P%0Js9p$H$7$F5s$2$F$$$F!"(B
max$B$H$O$I$&$"$k$Y$-$+$^$G$OF’$_9~$s$G$J$$$D$b$j$G$9!#(B

$B$=$&$$$&5DO@$r$9$k$J$i$I$C$A$+$H$$$&$H!"(B#4577$B$G$9$+$M!#!#(B

Issue #4591 has been updated by Koichi Sasada.

Status changed from Open to Assigned
Assignee set to Kenta M.


Bug #4591: (1.5…2).max #=> 1 (Range#max)

Author: Masaya T.
Status: Assigned
Priority: Normal
Assignee: Kenta M.
Category: core
Target version:
ruby -v: -

=begin

現在、(1.5…2).maxが1になります。
beginより小さい値が返ってくるのは違和感があります。

終端を含まないRangeについて、endがIntegerである時にend-1を返していますが、
それはbeginもIntegerである事が想定されてると思います。
結局 beginがIntegerじゃないときは、endがIntegerでない時と同様に
maxが定義できなさそうなので、やはり同様にErrorにした方が親切かと思います。

以下のパッチを入れていいですか?

Index: range.c

— range.c (リビジョン 31313)
+++ range.c (作業コピー)
@@ -670,6 +670,9 @@
rb_raise(rb_eTypeError, “cannot exclude non Integer end
value”);

        }
        if (c == 0) return Qnil;
  •       if (!FIXNUM_P(b) && !rb_obj_is_kind_of(b,rb_cInteger)) {
    
  •           rb_raise(rb_eTypeError, "cannot exclude end value with 
    

non Integer begin value");

  •       }
          if (FIXNUM_P(e)) {
              return LONG2NUM(FIX2LONG(e) - 1);
          }
    

Index: test/ruby/test_range.rb

— test/ruby/test_range.rb (リビジョン 31313)
+++ test/ruby/test_range.rb (作業コピー)
@@ -68,6 +68,8 @@
assert_equal(2.0, (1.0…2.0).max)
assert_equal(nil, (2.0…1.0).max)
assert_raise(TypeError) { (1.0…2.0).max }

  • assert_raise(TypeError) { (1…1.5).max }

  • assert_raise(TypeError) { (1.5…2).max }

    assert_equal(-0x80000002, ((-0x80000002)…(-0x80000001)).max)

=end

Issue #4591 has been updated by Hiroshi NAKAMURA.

Target version set to 1.9.3


Bug #4591: (1.5…2).max #=> 1 (Range#max)

Author: Masaya T.
Status: Assigned
Priority: Normal
Assignee: Kenta M.
Category: core
Target version: 1.9.3
ruby -v: -

=begin

現在、(1.5…2).maxが1になります。
beginより小さい値が返ってくるのは違和感があります。

終端を含まないRangeについて、endがIntegerである時にend-1を返していますが、
それはbeginもIntegerである事が想定されてると思います。
結局 beginがIntegerじゃないときは、endがIntegerでない時と同様に
maxが定義できなさそうなので、やはり同様にErrorにした方が親切かと思います。

以下のパッチを入れていいですか?

Index: range.c

— range.c (リビジョン 31313)
+++ range.c (作業コピー)
@@ -670,6 +670,9 @@
rb_raise(rb_eTypeError, “cannot exclude non Integer end
value”);

        }
        if (c == 0) return Qnil;
  •       if (!FIXNUM_P(b) && !rb_obj_is_kind_of(b,rb_cInteger)) {
    
  •           rb_raise(rb_eTypeError, "cannot exclude end value with 
    

non Integer begin value");

  •       }
          if (FIXNUM_P(e)) {
              return LONG2NUM(FIX2LONG(e) - 1);
          }
    

Index: test/ruby/test_range.rb

— test/ruby/test_range.rb (リビジョン 31313)
+++ test/ruby/test_range.rb (作業コピー)
@@ -68,6 +68,8 @@
assert_equal(2.0, (1.0…2.0).max)
assert_equal(nil, (2.0…1.0).max)
assert_raise(TypeError) { (1.0…2.0).max }

  • assert_raise(TypeError) { (1…1.5).max }

  • assert_raise(TypeError) { (1.5…2).max }

    assert_equal(-0x80000002, ((-0x80000002)…(-0x80000001)).max)

=end

$B$^$D$b$H(B $B$f$-$R$m$G$9(B

In message “Re: [ruby-dev:43732] [Ruby 1.9 - Bug #4591][Assigned]
(1.5…2).max #=> 1 (Range#max)”
on Sat, 11 Jun 2011 17:03:00 +0900, Koichi Sasada
[email protected] writes:

|$B=*C<$r4^$^$J$$(BRange$B$K$D$$$F!“(Bend$B$,(BInteger$B$G$”$k;~$K(Bend-1$B$rJV$7$F$$$^$9$,!“(B
|$B$=$l$O(Bbegin$B$b(BInteger$B$G$”$k;v$,A[Dj$5$l$F$k$H;W$$$^$9!#(B
|$B7k6I(B
begin$B$,(BInteger$B$8$c$J$$$H$-$O!“(Bend$B$,(BInteger$B$G$J$$;~$HF1MM$K(B
|max$B$,Dj5A$G$-$J$5$=$&$J$N$G!”$d$O$jF1MM$K(BError$B$K$7$?J}$,?F@Z$+$H;W$$$^$9!#(B
|
|$B0J2<$N%Q%C%A$rF~$l$F$$$$$G$9$+!)(B

$BF~$l$F$b$$$$$s$8$c$J$$$G$7$g$&$+!#(B

Issue #4591 has been updated by Yui NARUSE.

Assignee changed from Kenta M. to Masaya T.


Bug #4591: (1.5…2).max #=> 1 (Range#max)

Author: Masaya T.
Status: Assigned
Priority: Normal
Assignee: Masaya T.
Category: core
Target version: 1.9.3
ruby -v: -

現在、(1.5…2).maxが1になります。
beginより小さい値が返ってくるのは違和感があります。

終端を含まないRangeについて、endがIntegerである時にend-1を返していますが、
それはbeginもIntegerである事が想定されてると思います。
結局 beginがIntegerじゃないときは、endがIntegerでない時と同様に
maxが定義できなさそうなので、やはり同様にErrorにした方が親切かと思います。

以下のパッチを入れていいですか?

Index: range.c

— range.c (リビジョン 31313)
+++ range.c (作業コピー)
@@ -670,6 +670,9 @@
rb_raise(rb_eTypeError, “cannot exclude non Integer end
value”);

        }
        if (c == 0) return Qnil;
  •       if (!FIXNUM_P(b) && !rb_obj_is_kind_of(b,rb_cInteger)) {
    
  •           rb_raise(rb_eTypeError, "cannot exclude end value with 
    

non Integer begin value");

  •       }
          if (FIXNUM_P(e)) {
              return LONG2NUM(FIX2LONG(e) - 1);
          }
    

Index: test/ruby/test_range.rb

— test/ruby/test_range.rb (リビジョン 31313)
+++ test/ruby/test_range.rb (作業コピー)
@@ -68,6 +68,8 @@
assert_equal(2.0, (1.0…2.0).max)
assert_equal(nil, (2.0…1.0).max)
assert_raise(TypeError) { (1.0…2.0).max }

  • assert_raise(TypeError) { (1…1.5).max }

  • assert_raise(TypeError) { (1.5…2).max }

    assert_equal(-0x80000002, ((-0x80000002)…(-0x80000001)).max)

Issue #4591 has been updated by Kenta M…

今回のbug報告はこの時の変更に追従して変更すべきだったのを
変更してなかったんではないかというバグ報告として挙げていて、
maxとはどうあるべきかまでは踏み込んでないつもりです。

そういう議論をするならどっちかというと、#4577ですかね。。

この issue が max の定義についての議論ではなく、
以前に報告された bug への対応漏れという認識で理解しました。


Bug #4591: (1.5…2).max #=> 1 (Range#max)

Author: Masaya T.
Status: Assigned
Priority: Normal
Assignee: Kenta M.
Category: core
Target version: 1.9.3
ruby -v: -

=begin

現在、(1.5…2).maxが1になります。
beginより小さい値が返ってくるのは違和感があります。

終端を含まないRangeについて、endがIntegerである時にend-1を返していますが、
それはbeginもIntegerである事が想定されてると思います。
結局 beginがIntegerじゃないときは、endがIntegerでない時と同様に
maxが定義できなさそうなので、やはり同様にErrorにした方が親切かと思います。

以下のパッチを入れていいですか?

Index: range.c

— range.c (リビジョン 31313)
+++ range.c (作業コピー)
@@ -670,6 +670,9 @@
rb_raise(rb_eTypeError, “cannot exclude non Integer end
value”);

        }
        if (c == 0) return Qnil;
  •       if (!FIXNUM_P(b) && !rb_obj_is_kind_of(b,rb_cInteger)) {
    
  •           rb_raise(rb_eTypeError, "cannot exclude end value with 
    

non Integer begin value");

  •       }
          if (FIXNUM_P(e)) {
              return LONG2NUM(FIX2LONG(e) - 1);
          }
    

Index: test/ruby/test_range.rb

— test/ruby/test_range.rb (リビジョン 31313)
+++ test/ruby/test_range.rb (作業コピー)
@@ -68,6 +68,8 @@
assert_equal(2.0, (1.0…2.0).max)
assert_equal(nil, (2.0…1.0).max)
assert_raise(TypeError) { (1.0…2.0).max }

  • assert_raise(TypeError) { (1…1.5).max }

  • assert_raise(TypeError) { (1.5…2).max }

    assert_equal(-0x80000002, ((-0x80000002)…(-0x80000001)).max)

=end