зеркало из https://github.com/github/ruby.git
Do not round `a**b` to infinity
... instead, just calculate the value unless it is too big. Also, this change raises an ArgumentError if it is expected to exceed 16 GB in a 64-bit environment. (It is possible to calculate it straightforward, but it would likely be out-of-memory, so I didn't think it would make sense.) [Feature #20811]
This commit is contained in:
Родитель
f7b334e002
Коммит
45cd4a8296
12
bignum.c
12
bignum.c
|
@ -6287,8 +6287,7 @@ rb_big_pow(VALUE x, VALUE y)
|
|||
y = bignorm(y);
|
||||
if (FIXNUM_P(y))
|
||||
goto again;
|
||||
rb_warn("in a**b, b may be too big");
|
||||
d = rb_big2dbl(y);
|
||||
rb_raise(rb_eArgError, "exponent is too large");
|
||||
}
|
||||
else if (FIXNUM_P(y)) {
|
||||
yy = FIX2LONG(y);
|
||||
|
@ -6304,13 +6303,16 @@ rb_big_pow(VALUE x, VALUE y)
|
|||
VALUE z = 0;
|
||||
SIGNED_VALUE mask;
|
||||
const size_t xbits = rb_absint_numwords(x, 1, NULL);
|
||||
const size_t BIGLEN_LIMIT = 32*1024*1024;
|
||||
#if SIZEOF_SIZE_T == 4
|
||||
const size_t BIGLEN_LIMIT = 1ULL << 31; // 2 GB
|
||||
#else // SIZEOF_SIZE_T == 8
|
||||
const size_t BIGLEN_LIMIT = 1ULL << 34; // 16 GB
|
||||
#endif
|
||||
|
||||
if (xbits == (size_t)-1 ||
|
||||
(xbits > BIGLEN_LIMIT) ||
|
||||
(xbits * yy > BIGLEN_LIMIT)) {
|
||||
rb_warn("in a**b, b may be too big");
|
||||
d = (double)yy;
|
||||
rb_raise(rb_eArgError, "exponent is too large");
|
||||
}
|
||||
else {
|
||||
for (mask = FIXNUM_MAX + 1; mask; mask >>= 1) {
|
||||
|
|
|
@ -1048,8 +1048,7 @@ rb_rational_pow(VALUE self, VALUE other)
|
|||
}
|
||||
}
|
||||
else if (RB_BIGNUM_TYPE_P(other)) {
|
||||
rb_warn("in a**b, b may be too big");
|
||||
return rb_float_pow(nurat_to_f(self), other);
|
||||
rb_raise(rb_eArgError, "exponent is too large");
|
||||
}
|
||||
else if (RB_FLOAT_TYPE_P(other) || RB_TYPE_P(other, T_RATIONAL)) {
|
||||
return rb_float_pow(nurat_to_f(self), other);
|
||||
|
|
|
@ -48,10 +48,18 @@ describe :integer_exponent, shared: true do
|
|||
(-1).send(@method, 4611686018427387905).should eql(-1)
|
||||
end
|
||||
|
||||
it "returns Float::INFINITY when the number is too big" do
|
||||
-> {
|
||||
2.send(@method, 427387904).should == Float::INFINITY
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
ruby_version_is ""..."3.4" do
|
||||
it "returns Float::INFINITY when the number is too big" do
|
||||
-> {
|
||||
2.send(@method, 427387904).should == Float::INFINITY
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
end
|
||||
end
|
||||
|
||||
ruby_version_is "3.4" do
|
||||
it "raises an ArgumentError when the number is too big" do
|
||||
-> { 100000000.send(@method, 1000000000) }.should raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
it "raises a ZeroDivisionError for 0 ** -1" do
|
||||
|
@ -108,13 +116,23 @@ describe :integer_exponent, shared: true do
|
|||
-> { @bignum.send(@method, :symbol) }.should raise_error(TypeError)
|
||||
end
|
||||
|
||||
it "switch to a Float when the values is too big" do
|
||||
flt = nil
|
||||
-> {
|
||||
flt = @bignum.send(@method, @bignum)
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
flt.should be_kind_of(Float)
|
||||
flt.infinite?.should == 1
|
||||
ruby_version_is ""..."3.4" do
|
||||
it "switch to a Float when the values is too big" do
|
||||
flt = nil
|
||||
-> {
|
||||
flt = @bignum.send(@method, @bignum)
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
flt.should be_kind_of(Float)
|
||||
flt.infinite?.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
ruby_version_is "3.4" do
|
||||
it "does not switch to a Float when the values is too big" do
|
||||
-> {
|
||||
@bignum.send(@method, @bignum)
|
||||
}.should raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns a complex number when negative and raised to a fractional power" do
|
||||
|
|
|
@ -84,45 +84,89 @@ describe :rational_exponent, shared: true do
|
|||
(Rational(-1) ** bignum_value(3)).should eql(Rational(-1))
|
||||
end
|
||||
|
||||
it "returns positive Infinity when self is > 1" do
|
||||
-> {
|
||||
(Rational(2) ** bignum_value).infinite?.should == 1
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
-> {
|
||||
(Rational(fixnum_max) ** bignum_value).infinite?.should == 1
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
ruby_version_is ""..."3.4" do
|
||||
it "returns positive Infinity when self is > 1" do
|
||||
-> {
|
||||
(Rational(2) ** bignum_value).infinite?.should == 1
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
-> {
|
||||
(Rational(fixnum_max) ** bignum_value).infinite?.should == 1
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
end
|
||||
|
||||
it "returns 0.0 when self is > 1 and the exponent is negative" do
|
||||
-> {
|
||||
(Rational(2) ** -bignum_value).should eql(0.0)
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
-> {
|
||||
(Rational(fixnum_max) ** -bignum_value).should eql(0.0)
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns 0.0 when self is > 1 and the exponent is negative" do
|
||||
-> {
|
||||
(Rational(2) ** -bignum_value).should eql(0.0)
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
-> {
|
||||
(Rational(fixnum_max) ** -bignum_value).should eql(0.0)
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
ruby_version_is "3.4" do
|
||||
it "raises an ArgumentError when self is > 1" do
|
||||
-> {
|
||||
(Rational(2) ** bignum_value)
|
||||
}.should raise_error(ArgumentError)
|
||||
-> {
|
||||
(Rational(fixnum_max) ** bignum_value)
|
||||
}.should raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "raises an ArgumentError when self is > 1 and the exponent is negative" do
|
||||
-> {
|
||||
(Rational(2) ** -bignum_value)
|
||||
}.should raise_error(ArgumentError)
|
||||
-> {
|
||||
(Rational(fixnum_max) ** -bignum_value)
|
||||
}.should raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
# Fails on linux due to pow() bugs in glibc: http://sources.redhat.com/bugzilla/show_bug.cgi?id=3866
|
||||
platform_is_not :linux do
|
||||
it "returns positive Infinity when self < -1" do
|
||||
-> {
|
||||
(Rational(-2) ** bignum_value).infinite?.should == 1
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
-> {
|
||||
(Rational(-2) ** (bignum_value + 1)).infinite?.should == 1
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
-> {
|
||||
(Rational(fixnum_min) ** bignum_value).infinite?.should == 1
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
ruby_version_is ""..."3.4" do
|
||||
it "returns positive Infinity when self < -1" do
|
||||
-> {
|
||||
(Rational(-2) ** bignum_value).infinite?.should == 1
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
-> {
|
||||
(Rational(-2) ** (bignum_value + 1)).infinite?.should == 1
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
-> {
|
||||
(Rational(fixnum_min) ** bignum_value).infinite?.should == 1
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
end
|
||||
|
||||
it "returns 0.0 when self is < -1 and the exponent is negative" do
|
||||
-> {
|
||||
(Rational(-2) ** -bignum_value).should eql(0.0)
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
-> {
|
||||
(Rational(fixnum_min) ** -bignum_value).should eql(0.0)
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns 0.0 when self is < -1 and the exponent is negative" do
|
||||
-> {
|
||||
(Rational(-2) ** -bignum_value).should eql(0.0)
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
-> {
|
||||
(Rational(fixnum_min) ** -bignum_value).should eql(0.0)
|
||||
}.should complain(/warning: in a\*\*b, b may be too big/)
|
||||
ruby_version_is "3.4" do
|
||||
it "returns positive Infinity when self < -1" do
|
||||
-> {
|
||||
(Rational(-2) ** bignum_value)
|
||||
}.should raise_error(ArgumentError)
|
||||
-> {
|
||||
(Rational(fixnum_min) ** bignum_value)
|
||||
}.should raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "returns 0.0 when self is < -1 and the exponent is negative" do
|
||||
-> {
|
||||
(Rational(-2) ** -bignum_value)
|
||||
}.should raise_error(ArgumentError)
|
||||
-> {
|
||||
(Rational(fixnum_min) ** -bignum_value)
|
||||
}.should raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -476,8 +476,8 @@ class TestBignum < Test::Unit::TestCase
|
|||
def test_pow
|
||||
assert_equal(1.0, T32 ** 0.0)
|
||||
assert_equal(1.0 / T32, T32 ** -1)
|
||||
assert_equal(1, assert_warning(/may be too big/) {T32 ** T32}.infinite?)
|
||||
assert_equal(1, assert_warning(/may be too big/) {T32 ** (2**30-1)}.infinite?)
|
||||
assert_raise(ArgumentError) { T32 ** T32 }
|
||||
assert_raise(ArgumentError) { T32 ** (2**30-1) }
|
||||
|
||||
### rational changes the behavior of Bignum#**
|
||||
#assert_raise(TypeError) { T32**"foo" }
|
||||
|
|
|
@ -57,20 +57,19 @@ class TestInteger < Test::Unit::TestCase
|
|||
nil
|
||||
end, "[ruby-dev:32084] [ruby-dev:34547]")
|
||||
|
||||
x = EnvUtil.suppress_warning {2 ** -0x4000000000000000}
|
||||
assert_in_delta(0.0, (x / 2), Float::EPSILON)
|
||||
assert_raise(ArgumentError) {2 ** -0x4000000000000000}
|
||||
|
||||
<<~EXPRS.each_line.with_index(__LINE__+1) do |expr, line|
|
||||
crash01: 111r+11**-11111161111111
|
||||
crash02: 1118111111111**-1111111111111111**1+1==11111
|
||||
crash03: -1111111**-1111*11 - -1111111** -111111111
|
||||
crash03: -1111111**-1111*11 - -11** -11111111
|
||||
crash04: 1118111111111** -1111111111111111**1+11111111111**1 ===111
|
||||
crash05: 11** -111155555555555555 -55 !=5-555
|
||||
crash07: 1 + 111111111**-1111811111
|
||||
crash08: 18111111111**-1111111111111111**1 + 1111111111**-1111**1
|
||||
crash10: -7 - -1111111** -1111**11
|
||||
crash12: 1118111111111** -1111111111111111**1 + 1111 - -1111111** -1111*111111111119
|
||||
crash13: 1.0i - -1111111** -111111111
|
||||
crash13: 1.0i - -11** -11111111
|
||||
crash14: 11111**111111111**111111 * -11111111111111111111**-111111111111
|
||||
crash15: ~1**1111 + -~1**~1**111
|
||||
crash17: 11** -1111111**1111 /11i
|
||||
|
@ -80,7 +79,7 @@ class TestInteger < Test::Unit::TestCase
|
|||
crash21: 11**-10111111119-1i -1r
|
||||
EXPRS
|
||||
name, expr = expr.split(':', 2)
|
||||
assert_ruby_status(%w"-W0", expr, name)
|
||||
assert_ruby_status(%w"-W0", "begin; #{ expr }; rescue ArgumentError; end", name)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1066,11 +1066,10 @@ class Rational_Test < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def test_power_overflow
|
||||
bug = '[ruby-core:79686] [Bug #13242]: Infinity due to overflow'
|
||||
x = EnvUtil.suppress_warning {4r**40000000}
|
||||
assert_predicate x, :infinite?, bug
|
||||
x = EnvUtil.suppress_warning {(1/4r)**40000000}
|
||||
assert_equal 0, x, bug
|
||||
assert_raise(ArgumentError) { 4r**400000000000000000000 }
|
||||
exp = 4**40000000
|
||||
assert_equal exp, 4r**40000000
|
||||
assert_equal 1r/exp, (1/4r)**40000000
|
||||
end
|
||||
|
||||
def test_positive_p
|
||||
|
|
Загрузка…
Ссылка в новой задаче