Bug #2662

BigDecimal#ceil, etc. should not return Integer

Added by mame (Yusuke Endoh) over 2 years ago. Updated about 1 year ago.

[ruby-dev:40168]
Status:Rejected Start date:01/27/2010
Priority:Normal Due date:
Assignee:yugui (Yuki Sonoda) % Done:

0%

Category:ext
Target version:2.0.0
ruby -v:ruby 1.9.2dev (2010-01-27 trunk 26434) [i686-linux]

Description

まつもとさん
遠藤です。

r20584 と r20616 で BigDecimal#ceil 、truncate 、floor 、round 、div の
戻り値を Integer にする変更がありますが、これだと巨大な BigDecimal を
扱えなく、または扱いが面倒になります。


  # 巨大な BigDecimal は ceil が取れない
  $ ./ruby -rbigdecimal -e 'p BigDecimal("1E100000").ceil'
  (巨大な Bignum を確保しようとして固まる...)

  # Infinity が混ざるときは自分で対処する必要がある
  $ ./ruby -rbigdecimal -e 'p BigDecimal("Infinity").ceil'
  -e:1:in `ceil': Computation results to 'Infinity' (FloatDomainError)
          from -e:1:in `<main>'


Float#ceil などが Integer を返すのに合わせるためだと思いますが、この変更
では BigDecimal のありがたみ自体が減ってしまう気がします。
どうしてもということでなければ、revert を提案します。


diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c
index c6ffe98..1f51f61 100644
--- a/ext/bigdecimal/bigdecimal.c
+++ b/ext/bigdecimal/bigdecimal.c
@@ -1108,7 +1108,7 @@ BigDecimal_div2(int argc, VALUE *argv, VALUE self)
        Real *div=NULL;
        Real *mod;
        if(BigDecimal_DoDivmod(self,b,&div,&mod)) {
-	  return BigDecimal_to_i(ToValue(div));
+	  return ToValue(div);
        }
        return DoSomeOne(self,b,rb_intern("div"));
     } else {    /* div in BigDecimal sense */
@@ -1308,9 +1308,6 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self)
     GUARD_OBJ(c,VpCreateRbObject(mx, "0"));
     VpSetPrecLimit(pl);
     VpActiveRound(c,a,sw,iLoc);
-    if (argc == 0) {
-	return BigDecimal_to_i(ToValue(c));
-    }
     return ToValue(c);
 }

@@ -1355,9 +1352,6 @@ BigDecimal_truncate(int argc, VALUE *argv, VALUE self)
     GUARD_OBJ(c,VpCreateRbObject(mx, "0"));
     VpSetPrecLimit(pl);
     VpActiveRound(c,a,VP_ROUND_DOWN,iLoc); /* 0: truncate */
-    if (argc == 0) {
-	return BigDecimal_to_i(ToValue(c));
-    }
     return ToValue(c);
 }

@@ -1418,9 +1412,6 @@ BigDecimal_floor(int argc, VALUE *argv, VALUE self)
     GUARD_OBJ(c,VpCreateRbObject(mx, "0"));
     VpSetPrecLimit(pl);
     VpActiveRound(c,a,VP_ROUND_FLOOR,iLoc);
-    if (argc == 0) {
-	return BigDecimal_to_i(ToValue(c));
-    }
     return ToValue(c);
 }

@@ -1465,9 +1456,6 @@ BigDecimal_ceil(int argc, VALUE *argv, VALUE self)
     GUARD_OBJ(c,VpCreateRbObject(mx, "0"));
     VpSetPrecLimit(pl);
     VpActiveRound(c,a,VP_ROUND_CEIL,iLoc);
-    if (argc == 0) {
-	return BigDecimal_to_i(ToValue(c));
-    }
     return ToValue(c);
 }



ついでに、この変更で rubyspec がやっぱりいっぱい失敗しています。


2)
BigDecimal#ceil returns the smallest integer greater or equal to self, if n is unspecified ERROR
FloatDomainError: Computation results to 'Infinity'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/ceil_spec.rb:34:in `ceil'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/ceil_spec.rb:34:in `block (2 levels) in <top (required)>'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/ceil_spec.rb:4:in `<top (required)>'

3)
BigDecimal#div with precision set to 0 returns NaN if NaN is involved ERROR
FloatDomainError: Computation results to 'NaN'(Not a Number)
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/shared/quo.rb:34:in `div'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/shared/quo.rb:34:in `block (2 levels) in <top (required)>'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/div_spec.rb:5:in `<top (required)>'

4)
BigDecimal#div returns NaN if NaN is involved ERROR
FloatDomainError: Computation results to 'NaN'(Not a Number)
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/div_spec.rb:46:in `div'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/div_spec.rb:46:in `block (2 levels) in <top (required)>'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/div_spec.rb:12:in `<top (required)>'

5)
BigDecimal#div returns NaN if divided by Infinity and no precision given ERROR
FloatDomainError: Computation results to 'NaN'(Not a Number)
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/div_spec.rb:51:in `div'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/div_spec.rb:51:in `block (2 levels) in <top (required)>'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/div_spec.rb:12:in `<top (required)>'

6)
BigDecimal#div returns NaN if (+|-) Infinity divided by 1 and no precision given ERROR
FloatDomainError: Computation results to 'NaN'(Not a Number)
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/div_spec.rb:109:in `div'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/div_spec.rb:109:in `block (2 levels) in <top (required)>'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/div_spec.rb:12:in `<top (required)>'

8)
BigDecimal#floor returns the greatest integer smaller or equal to self ERROR
FloatDomainError: Computation results to 'Infinity'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/floor_spec.rb:29:in `floor'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/floor_spec.rb:29:in `block (2 levels) in <top (required)>'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/floor_spec.rb:4:in `<top (required)>'

13)
BigDecimal#truncate returns value of type Bigdecimal. FAILED
Expected false
 to equal true

/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/truncate_spec.rb:16:in `block (3 levels) in <top (required)>'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/truncate_spec.rb:15:in `each'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/truncate_spec.rb:15:in `block (2 levels) in <top (required)>'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/truncate_spec.rb:4:in `<top (required)>'

14)
BigDecimal#truncate returns NaN if self is NaN ERROR
FloatDomainError: Computation results to 'NaN'(Not a Number)
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/truncate_spec.rb:64:in `truncate'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/truncate_spec.rb:64:in `block (2 levels) in <top (required)>'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/truncate_spec.rb:4:in `<top (required)>'

15)
BigDecimal#truncate returns Infinity if self is infinite ERROR
FloatDomainError: Computation results to 'Infinity'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/truncate_spec.rb:71:in `truncate'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/truncate_spec.rb:71:in `block (2 levels) in <top (required)>'
/home/mame/work/ruby/spec/rubyspec/library/bigdecimal/truncate_spec.rb:4:in `<top (required)>'

-- 
Yusuke Endoh <mame@tsg.ne.jp>

History

Updated by tadf (tadayoshi funaba) over 2 years ago

ちょっと試してみました。

> r20584 と r20616 で BigDecimal#ceil 、truncate 、floor 、round 、div の
> 戻り値を Integer にする変更がありますが、これだと巨大な BigDecimal を
> 扱えなく、または扱いが面倒になります。
> 
> 
>   # 巨大な BigDecimal は ceil が取れない
>   $ ./ruby -rbigdecimal -e 'p BigDecimal("1E100000").ceil'
>   (巨大な Bignum を確保しようとして固まる...)

僕のところでは固まるということはないですね。Rational と比べるとちょっと
遅いみたいですが。

b = BigDecimal("1E100000").ceil
r = Rational("1E100000").ceil
b == r #=> true

手元で試したところでは to_r で Rational を経由してから to_i したほうが
速いみたいです (つまり ceil(0).to_r.to_i のほうが、ceil よりも速い)。よ
うするに BigDecimal#to_i が遅いというのが本質ではないですか。

>   # Infinity が混ざるときは自分で対処する必要がある
>   $ ./ruby -rbigdecimal -e 'p BigDecimal("Infinity").ceil'
>   -e:1:in `ceil': Computation results to 'Infinity' (FloatDomainError)
>           from -e:1:in `<main>'

Infinity に対して ceil が取れないのは Float でも同じなのでそれが間違っ
ているなら、それは BigDecimal だけの問題でもないと思います。

> Float#ceil などが Integer を返すのに合わせるためだと思いますが、この変更
> では BigDecimal のありがたみ自体が減ってしまう気がします。
> どうしてもということでなければ、revert を提案します。

望むなら ceil を BigDecimal のまま取ることも出来るので (ceil(0))、提案
の理由で直ちに revert する理由はないように見えます。rubyspec のほうは判
りません。

Updated by mame (Yusuke Endoh) over 2 years ago

2010年1月27日19:12 Tadayoshi Funaba <tadf@dotrb.org>:
>>   # 巨大な BigDecimal は ceil が取れない
>>   $ ./ruby -rbigdecimal -e 'p BigDecimal("1E100000").ceil'
>>   (巨大な Bignum を確保しようとして固まる...)
>
> 僕のところでは固まるということはないですね。Rational と比べるとちょっと
> 遅いみたいですが。
>
> b = BigDecimal("1E100000").ceil
> r = Rational("1E100000").ceil
> b == r #=> true
>
> 手元で試したところでは to_r で Rational を経由してから to_i したほうが
> 速いみたいです (つまり ceil(0).to_r.to_i のほうが、ceil よりも速い)。よ
> うするに BigDecimal#to_i が遅いというのが本質ではないですか。

0 が一個足りませんでした。1E100000 はうちだと 8 秒ですね。1E1000000
だとどうでしょう。
しかし、Rational で動くなら、何かを直せばなんとかなりそうですね。

>
>>   # Infinity が混ざるときは自分で対処する必要がある
>>   $ ./ruby -rbigdecimal -e 'p BigDecimal("Infinity").ceil'
>>   -e:1:in `ceil': Computation results to 'Infinity' (FloatDomainError)
>>           from -e:1:in `<main>'
>
> Infinity に対して ceil が取れないのは Float でも同じなのでそれが間違っ
> ているなら、それは BigDecimal だけの問題でもないと思います。

BigDecimal に関しては 1.9.1 なら大丈夫だったのに、1.9.2 で大丈夫じゃ
なくなってしまうという問題です。


>> Float#ceil などが Integer を返すのに合わせるためだと思いますが、この変更
>> では BigDecimal のありがたみ自体が減ってしまう気がします。
>> どうしてもということでなければ、revert を提案します。
>
> 望むなら ceil を BigDecimal のまま取ることも出来るので (ceil(0))、提案
> の理由で直ちに revert する理由はないように見えます。rubyspec のほうは判
> りません。

「提案の理由で直ちに revert する理由はない」というより、「どうしても
変えないと困る理由がある」でないのであれば、1.9 系列の途中でむやみに
仕様変更しないほうがいいと思います。理由は一貫性だけなんでしょうか。

-- 
Yusuke ENDOH <mame@tsg.ne.jp>

Updated by tadf (tadayoshi funaba) over 2 years ago

> 0 が一個足りませんでした。1E100000 はうちだと 8 秒ですね。1E1000000
> だとどうでしょう。
> しかし、Rational で動くなら、何かを直せばなんとかなりそうですね。

Rational 経由だと 0 ひとつ増えてもそこそこの速度で返るようですね。

関係あるかわからないけど、BigDecimal#** が結構遅かった記憶があります。
あと、

BigDecimal('3') ** BigDecimal('3')
  # TypeError: wrong argument type BigDecimal (expected Fixnum)

BigDecimal('3') ** 3.3
  # TypeError: wrong argument type Float (expected Fixnum)

といったところで、Fixnum に比べても制限も多いし、実装にいろいろ手抜きが
あるってことなのかな。

> 「提案の理由で直ちに revert する理由はない」というより、「どうしても
> 変えないと困る理由がある」でないのであれば、1.9 系列の途中でむやみに
> 仕様変更しないほうがいいと思います。理由は一貫性だけなんでしょうか。

答える立場にないけど、理由は一貫性で間違ってないと思います。単に一貫性
を与えてみた、ということではなく、一貫性が必要だと思ったからそうしたの
だと思いますが。互換性を確保すべきなのかどうかは考え方次第だと思うので
僕には判りませんが。

Updated by matz (Yukihiro Matsumoto) over 2 years ago

まつもと ゆきひろです

In message "Re: [ruby-dev:40168] [Bug #2662] BigDecimal#ceil, etc. should not return Integer"
    on Wed, 27 Jan 2010 02:42:38 +0900, Yusuke Endoh <redmine@ruby-lang.org> writes:

|r20584 と r20616 で BigDecimal#ceil 、truncate 、floor 、round 、div の
|戻り値を Integer にする変更がありますが、これだと巨大な BigDecimal を
|扱えなく、または扱いが面倒になります。
|
|
|  # 巨大な BigDecimal は ceil が取れない
|  $ ./ruby -rbigdecimal -e 'p BigDecimal("1E100000").ceil'
|  (巨大な Bignum を確保しようとして固まる...)
|
|  # Infinity が混ざるときは自分で対処する必要がある
|  $ ./ruby -rbigdecimal -e 'p BigDecimal("Infinity").ceil'
|  -e:1:in `ceil': Computation results to 'Infinity' (FloatDomainError)
|          from -e:1:in `<main>'

ふなばさんが指摘しておられるように、前者は性能の問題、後者は
整数では表現できない値(InfやNaN)がある問題です。これらは分け
て考えましょう。

まず、後者からですが、Rubyではceilなどは整数を返します。それ
に揃えた方がBigDecimalとしての使い勝手が上がるでしょう。つま
り、より他の数値と同様に振る舞う方が望ましいと言う考えです。
特にBigDecimalと概念的に近いFloatではInfやNaNは例外を発生さ
せますから。

前者はBigDecimal#to_iが、文字列化してそれをパースするという
手段を使っているせいです。これは純粋に手抜きですから、それを
改善すればすむと思います。

ということで、リバートの必要はなく、あえていうならば、現在
BigDecimal#to_iがInfやNaNに対してnilを返しているのを例外にす
るという変更を加えるべきなんじゃないかと思います。

                                まつもと ゆきひろ /:|)

Updated by mame (Yusuke Endoh) over 2 years ago

遠藤です。

2010年1月28日11:34 Yukihiro Matsumoto <matz@ruby-lang.org>:
> |  # 巨大な BigDecimal は ceil が取れない
> |  $ ./ruby -rbigdecimal -e 'p BigDecimal("1E100000").ceil'
> |  (巨大な Bignum を確保しようとして固まる...)
> |
> |  # Infinity が混ざるときは自分で対処する必要がある
> |  $ ./ruby -rbigdecimal -e 'p BigDecimal("Infinity").ceil'
> |  -e:1:in `ceil': Computation results to 'Infinity' (FloatDomainError)
> |          from -e:1:in `<main>'
>
> ということで、リバートの必要はなく、

了解しました。


> あえていうならば、現在
> BigDecimal#to_iがInfやNaNに対してnilを返しているのを例外にす
> るという変更を加えるべきなんじゃないかと思います。

Yugui さん、判断をお願いします。

-- 
Yusuke ENDOH <mame@tsg.ne.jp>

Updated by mame (Yusuke Endoh) over 2 years ago

  • Assignee changed from matz (Yukihiro Matsumoto) to yugui (Yuki Sonoda)

Updated by yugui (Yuki Sonoda) over 2 years ago

On 1/28/10 8:24 PM, Yusuke ENDOH wrote:
>> ということで、リバートの必要はなく、
(snip)
>> あえていうならば、現在
>> BigDecimal#to_iがInfやNaNに対してnilを返しているのを例外にす
>> るという変更を加えるべきなんじゃないかと思います。
> 
> Yugui さん、判断をお願いします。

両方とも、まつもとさんに賛同します。

-- 
Yugui <yugui@yugui.jp>
http://yugui.jp
私は私をDumpする

Updated by mame (Yusuke Endoh) over 2 years ago

遠藤です。

2010年1月30日14:02 Yugui (Yuki Sonoda) <yugui@yugui.jp>:
> On 1/28/10 8:24 PM, Yusuke ENDOH wrote:
>>> ということで、リバートの必要はなく、
> (snip)
>>> あえていうならば、現在
>>> BigDecimal#to_iがInfやNaNに対してnilを返しているのを例外にす
>>> るという変更を加えるべきなんじゃないかと思います。
>>
>> Yugui さん、判断をお願いします。
>
> 両方とも、まつもとさんに賛同します。

お返事ありがとうございます。
つまり、いずれ 1.9.1 にバックポートされるということですね。
それなら 1.9 からの仕様変更ということで rubyspec を更新しようと思います。

-- 
Yusuke ENDOH <mame@tsg.ne.jp>

Updated by yugui (Yuki Sonoda) over 2 years ago

2010/1/30 Yusuke ENDOH <mame@tsg.ne.jp>:
> お返事ありがとうございます。
> つまり、いずれ 1.9.1 にバックポートされるということですね。
> それなら 1.9 からの仕様変更ということで rubyspec を更新しようと思います。

仕様のミスは原則的に1.9.1にはバックポートしないことに決めたので、しません。
ただ、これは1.9.1という実装のバグと扱って良いと思います。また、1.9.2から挙動を変更することに異論はないです。

という意図でした。

-- 
Yuki Sonoda (Yugui)
yugui@yugui.jp
http://yugui.jp

Updated by mame (Yusuke Endoh) about 2 years ago

  • Status changed from Open to Rejected
遠藤です。

rubyspec 側で対処したので、このチケットは reject します。

-- 
Yusuke Endoh <mame@tsg.ne.jp>

Also available in: Atom PDF