Bug #4591

(1.5...2).max #=> 1 (Range#max)

Added by Masaya Tarui about 3 years ago. Updated almost 3 years ago.

[ruby-dev:43406]
Status:Closed
Priority:Normal
Assignee:Masaya Tarui
Category:core
Target version:1.9.3
ruby -v:- Backport:

Description

現在、(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 @@
rbraise(rbeTypeError, "cannot exclude non Integer end value");

        }
        if (c == 0) return Qnil;
  • if (!FIXNUMP(b) && !rbobjiskindof(b,rbcInteger)) {
  • rbraise(rbeTypeError, "cannot exclude end value with non Integer begin value");
  • } if (FIXNUMP(e)) { return LONG2NUM(FIX2LONG(e) - 1); } Index: test/ruby/testrange.rb =================================================================== --- test/ruby/testrange.rb (リビジョン 31313) +++ test/ruby/testrange.rb (作業コピー) @@ -68,6 +68,8 @@ assertequal(2.0, (1.0..2.0).max) assertequal(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)


Related issues

Related to ruby-trunk - Bug #974: Range#max で終了しないことがある Closed 01/04/2009
Related to ruby-trunk - Bug #4577: (int...float).max should not raise an error Rejected 04/14/2011

Associated revisions

Revision 32482
Added by Masaya Tarui almost 3 years ago

  • range.c (range_max): fix behavior with excluded end value. [Bug #4591]

History

#1 Updated by Anonymous almost 3 years ago

=begin

Issue #4591 has been reported by Masaya Tarui.


Bug #4591: (1.5...2).max #=> 1 (Range#max)
http://redmine.ruby-lang.org/issues/4591

Author: Masaya Tarui
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にした方が親切かと思います。

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

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

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

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

#2 Updated by Kenta Murata almost 3 years ago

  • ruby -v changed from ruby 1.9.3dev (2011-04-20 trunk 31311) [i386-mswin32_100] to -

=begin
むらたです。

testrange.rb の testmax を見てみました。

assertequal(2, (1..2).max)
assert
equal(nil, (2..1).max)
assert_equal(1, (1...2).max)

assertequal(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)

assertequal(0, (0..0).max)
assert
equal(nil, (0...0).max)

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

assertequal(nil, (2..1).max)
assert
equal(nil, (2.0..1.0).max)
assert_raise(TypeError) { (1.0...2.0).max }

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

assertraise(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/251) に含まれる
すべての実数を代表する数なので、範囲 1.0 ... 2.0 に整数 1 が含まれるかどうかは
基本的には文脈依存でしか判断できない問題になります。

# リテラルの 1.0 は基本的に 1 と等しいんでしょうけど、
# 何らかの計算過程で得られた 1.0 が 1 と等しいかどうかは値だけでは判断できませんから。

--
Kenta Murata
Sent with Sparrow
=end

#3 Updated by Masaya Tarui almost 3 years ago

=begin
樽家です。

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

assertequal(nil, (2..1).max)
assert
equal(nil, (2.0..1.0).max)
assert_raise(TypeError) { (1.0...2.0).max }

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

assertraise(TypeError) { (1.5...2).max }
assert
raise(TypeError) { (1...1.5).max }

Range#max の明確な定義が必要ですよね。
snip

ここで一貫性がないとされている
assert_raise(TypeError) { (1.0...2.0).max }
は#974で導入されてます。
で、その時の議論をみると、

matz> * 万人が納得する (万人じゃなくて多くが、でもいいけど)
matz> * かつ有用な
matz>最大値の唯一の定義は存在しなさそうだ、ということです

という理由でエラーにしています。
このときはendがfloatの時だけを問題にしてましたが、
だったらbeginがfloatの時も問題でしょう。

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

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

--
樽家昌也(Masaya TARUI)
No Tool,No Life.
=end

#4 Updated by Masaya Tarui almost 3 years ago

=begin
そもそも、beginとendのオブジェクト及びRangeのメソッドによって、
離散値として扱う場合と、連続量として扱う場合が混ざってしまってるんですよね。

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

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

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

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

離散値を扱うと思ってるとこれでもよさそうなんですが、
そうすると今度は(1..1.5).max #=>1.5
になっているのが1にしないといけなくなってしまい、割と大変更になりそうです。

--
樽家昌也(Masaya TARUI)
No Tool,No Life.
=end

#5 Updated by Koichi Sasada almost 3 years ago

  • Status changed from Open to Assigned
  • Assignee set to Kenta Murata

#6 Updated by Yukihiro Matsumoto almost 3 years ago

まつもと ゆきひろです

In message "Re: [Ruby 1.9 - Bug #4591]Assigned.max #=> 1 (Range#max)"
on Sat, 11 Jun 2011 17:03:00 +0900, Koichi Sasada redmine@ruby-lang.org writes:

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

入れてもいいんじゃないでしょうか。

#7 Updated by Yukihiro Matsumoto almost 3 years ago

まつもと ゆきひろです

In message "Re: [Ruby 1.9 - Bug #4591]Assigned.max #=> 1 (Range#max)"
on Sat, 11 Jun 2011 17:03:00 +0900, Koichi Sasada redmine@ruby-lang.org writes:

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

入れてもいいんじゃないでしょうか。

#8 Updated by Hiroshi Nakamura almost 3 years ago

  • Target version set to 1.9.3

#9 Updated by Kenta Murata almost 3 years ago

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

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

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

#10 Updated by Yui NARUSE almost 3 years ago

  • Assignee changed from Kenta Murata to Masaya Tarui

#11 Updated by Masaya Tarui almost 3 years ago

  • Status changed from Assigned to Closed
  • % Done changed from 0 to 100

This issue was solved with changeset r32482.
Masaya, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.


  • range.c (range_max): fix behavior with excluded end value. [Bug #4591]

Also available in: Atom PDF