Skip to content

Commit 46c0ad6

Browse files
authored
Strict BigDecimal("0.1e#{exponent}") exponent overflow/underflow check (#369)
1 parent 15b7e0a commit 46c0ad6

3 files changed

Lines changed: 76 additions & 54 deletions

File tree

ext/bigdecimal/bigdecimal.c

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,14 @@
3838
#define SIGNED_VALUE_MAX INTPTR_MAX
3939
#define SIGNED_VALUE_MIN INTPTR_MIN
4040
#define MUL_OVERFLOW_SIGNED_VALUE_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX)
41+
#define ADD_OVERFLOW_SIGNED_VALUE_P(a, b) ADD_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX)
4142

4243
/* max_value = 0.9999_9999_9999E[exponent], exponent <= SIGNED_VALUE_MAX */
4344
#define VP_EXPONENT_MAX (SIGNED_VALUE_MAX / BASE_FIG)
4445
/* min_value = 0.0001_0000_0000E[exponent], exponent-(BASE_FIG-1) >= SIGNED_VALUE_MIN */
4546
#define VP_EXPONENT_MIN ((SIGNED_VALUE_MIN + BASE_FIG - 1) / BASE_FIG)
47+
#define EXPONENT_MAX (VP_EXPONENT_MAX * BASE_FIG)
48+
#define EXPONENT_MIN (VP_EXPONENT_MIN * BASE_FIG - (BASE_FIG - 1))
4649

4750
VALUE rb_cBigDecimal;
4851
VALUE rb_mBigMath;
@@ -6812,7 +6815,7 @@ VP_EXPORT int
68126815
VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, const char *exp_chr, size_t ne)
68136816
{
68146817
size_t i, j, ind_a, ma, mi, me;
6815-
SIGNED_VALUE e, es, eb, ef;
6818+
SIGNED_VALUE e;
68166819
int sign, signe, exponent_overflow;
68176820

68186821
/* get exponent part */
@@ -6835,23 +6838,13 @@ VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, con
68356838
++me;
68366839
}
68376840
while (i < me) {
6838-
if (MUL_OVERFLOW_SIGNED_VALUE_P(e, (SIGNED_VALUE)BASE_FIG)) {
6839-
es = e;
6840-
goto exp_overflow;
6841-
}
6842-
es = e * (SIGNED_VALUE)BASE_FIG;
6843-
if (MUL_OVERFLOW_SIGNED_VALUE_P(e, 10) ||
6844-
SIGNED_VALUE_MAX - (exp_chr[i] - '0') < e * 10)
6845-
goto exp_overflow;
6846-
e = e * 10 + exp_chr[i] - '0';
6847-
if (MUL_OVERFLOW_SIGNED_VALUE_P(e, (SIGNED_VALUE)BASE_FIG))
6848-
goto exp_overflow;
6849-
if (es > (SIGNED_VALUE)(e * BASE_FIG)) {
6850-
exp_overflow:
6841+
int dig = exp_chr[i] - '0';
6842+
if (MUL_OVERFLOW_SIGNED_VALUE_P(e, 10) ||
6843+
ADD_OVERFLOW_SIGNED_VALUE_P(e * 10, signe * dig)) {
68516844
exponent_overflow = 1;
6852-
e = es; /* keep sign */
68536845
break;
68546846
}
6847+
e = e * 10 + signe * dig;
68556848
++i;
68566849
}
68576850
}
@@ -6870,34 +6863,32 @@ VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, con
68706863
++mi;
68716864
}
68726865
}
6866+
/* skip leading zeros in integer part */
6867+
while (i < mi && int_chr[i] == '0') {
6868+
++i;
6869+
--ni;
6870+
}
68736871

6874-
e = signe * e; /* e: The value of exponent part. */
6875-
e = e + ni; /* set actual exponent size. */
6876-
6877-
if (e > 0) signe = 1;
6878-
else signe = -1;
6872+
/* set actual exponent size. */
6873+
if (ADD_OVERFLOW_SIGNED_VALUE_P(e, (SIGNED_VALUE)ni)) {
6874+
exponent_overflow = 1;
6875+
} else {
6876+
e += ni;
6877+
}
68796878

68806879
/* Adjust the exponent so that it is the multiple of BASE_FIG. */
6881-
j = 0;
6882-
ef = 1;
6883-
while (ef) {
6884-
if (e >= 0) eb = e;
6885-
else eb = -e;
6886-
ef = eb / (SIGNED_VALUE)BASE_FIG;
6887-
ef = eb - ef * (SIGNED_VALUE)BASE_FIG;
6888-
if (ef) {
6889-
++j; /* Means to add one more preceding zero */
6890-
++e;
6891-
}
6880+
j = (BASE_FIG - e % BASE_FIG) % BASE_FIG;
6881+
if (ADD_OVERFLOW_SIGNED_VALUE_P(e, (SIGNED_VALUE)j)) {
6882+
exponent_overflow = 1;
6883+
} else {
6884+
e += j;
68926885
}
68936886

6894-
eb = e / (SIGNED_VALUE)BASE_FIG;
6895-
6896-
if (exponent_overflow) {
6887+
if (exponent_overflow || e < EXPONENT_MIN || e > EXPONENT_MAX) {
68976888
int zero = 1;
68986889
for ( ; i < mi && zero; i++) zero = int_chr[i] == '0';
68996890
for (i = 0; i < nf && zero; i++) zero = frac[i] == '0';
6900-
if (!zero && signe > 0) {
6891+
if (!zero && e > 0) {
69016892
VpSetInf(a, sign);
69026893
VpException(VP_EXCEPTION_INFINITY, "exponent overflow",0);
69036894
}
@@ -6947,7 +6938,7 @@ VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, con
69476938
++j;
69486939
}
69496940
a->Prec = ind_a + 1;
6950-
a->exponent = eb;
6941+
a->exponent = e / (SIGNED_VALUE)BASE_FIG;
69516942
VpSetSign(a, sign);
69526943
VpNmlz(a);
69536944
return 1;

ext/bigdecimal/bits.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
((b) > 0 ? (max) / (a) < (b) : (min) / (a) > (b)) : \
2727
((b) > 0 ? (min) / (a) < (b) : (max) / (a) > (b)))
2828

29+
#define ADD_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \
30+
((a) > 0) == ((b) > 0) && ((a) > 0 ? (max) - (a) < (b) : (min) - (a) > (b)))
31+
2932
#ifdef HAVE_UINT128_T
3033
# define bit_length(x) \
3134
(unsigned int) \

test/bigdecimal/test_bigdecimal.rb

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ class TestBigDecimal < Test::Unit::TestCase
99
LIMITS = RbConfig::LIMITS
1010
else
1111
require 'fiddle'
12+
INTPTR_MAX = (1 << (Fiddle::SIZEOF_INTPTR_T*8 - 1)) - 1
13+
INTPTR_MIN = [INTPTR_MAX + 1].pack("L!").unpack("l!")[0]
1214
LONG_MAX = (1 << (Fiddle::SIZEOF_LONG*8 - 1)) - 1
1315
LONG_MIN = [LONG_MAX + 1].pack("L!").unpack("l!")[0]
1416
LLONG_MAX = (1 << (Fiddle::SIZEOF_LONG_LONG*8 - 1)) - 1
1517
LLONG_MIN = [LLONG_MAX + 1].pack("Q!").unpack("q!")[0]
1618
ULLONG_MAX = (1 << Fiddle::SIZEOF_LONG_LONG*8) - 1
1719
LIMITS = {
20+
"INTPTR_MAX" => INTPTR_MAX,
21+
"INTPTR_MIN" => INTPTR_MIN,
1822
"LLONG_MIN" => LLONG_MIN,
1923
"ULLONG_MAX" => ULLONG_MAX,
2024
"FIXNUM_MIN" => LONG_MIN / 2,
@@ -25,6 +29,9 @@ class TestBigDecimal < Test::Unit::TestCase
2529
}.freeze
2630
end
2731

32+
EXPONENT_MAX = LIMITS['INTPTR_MAX'] / BASE_FIG * BASE_FIG
33+
EXPONENT_MIN = (LIMITS['INTPTR_MIN'] - 2) / BASE_FIG * BASE_FIG + BASE_FIG + 1
34+
2835
ROUNDING_MODE_MAP = [
2936
[ BigDecimal::ROUND_UP, :up],
3037
[ BigDecimal::ROUND_DOWN, :down],
@@ -80,12 +87,24 @@ def test_BigDecimal
8087
assert_raise(ArgumentError) { BigDecimal("1", -1) }
8188
assert_raise_with_message(ArgumentError, /"1__1_1"/) { BigDecimal("1__1_1") }
8289
assert_raise_with_message(ArgumentError, /"_1_1_1"/) { BigDecimal("_1_1_1") }
90+
assert(BigDecimal("0.1E#{EXPONENT_MAX}").finite?)
91+
assert(BigDecimal("0.1E#{EXPONENT_MIN}").finite?)
8392

8493
BigDecimal.save_exception_mode do
8594
BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false)
95+
BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false)
8696
BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false)
8797
assert_positive_infinite(BigDecimal("Infinity"))
88-
assert_positive_infinite(BigDecimal("1E1111111111111111111"))
98+
assert_positive_infinite(BigDecimal("0.1E#{EXPONENT_MAX + 1}"))
99+
assert_negative_infinite(BigDecimal("-0.1E#{EXPONENT_MAX + 1}"))
100+
assert_positive_infinite(BigDecimal("1E#{EXPONENT_MAX}"))
101+
assert_negative_infinite(BigDecimal("-1E#{EXPONENT_MAX}"))
102+
assert_positive_zero(BigDecimal("0E#{EXPONENT_MAX + 1}"))
103+
assert_negative_zero(BigDecimal("-0E#{EXPONENT_MAX + 1}"))
104+
assert_positive_zero(BigDecimal("0.1E#{EXPONENT_MIN - 1}"))
105+
assert_negative_zero(BigDecimal("-0.1E#{EXPONENT_MIN - 1}"))
106+
assert_positive_zero(BigDecimal("0.01E#{EXPONENT_MIN}"))
107+
assert_negative_zero(BigDecimal("-0.01E#{EXPONENT_MIN}"))
89108
assert_positive_infinite(BigDecimal(" \t\n\r \rInfinity \t\n\r \r"))
90109
assert_negative_infinite(BigDecimal("-Infinity"))
91110
assert_negative_infinite(BigDecimal(" \t\n\r \r-Infinity \t\n\r \r"))
@@ -460,39 +479,48 @@ def test_exception_overflow
460479
end
461480
end
462481

482+
def test_add_sub_underflow
483+
BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false)
484+
x = BigDecimal("0.100000000002E#{EXPONENT_MIN + 10}")
485+
y = BigDecimal("0.100000000001E#{EXPONENT_MIN + 10}")
486+
z = BigDecimal("0.101E#{EXPONENT_MIN + 10}")
487+
assert_not_equal(0, x - z)
488+
assert_not_equal(0, z - y)
489+
assert_positive_zero(x + (-y))
490+
assert_positive_zero(x - y)
491+
assert_positive_zero((-y) - (-x))
492+
assert_negative_zero((-x) + y)
493+
assert_negative_zero(y - x)
494+
assert_negative_zero((-x) - (-y))
495+
end
496+
463497
def test_mult_div_overflow_underflow_sign
464498
BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false)
465499
BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false)
500+
large_x = BigDecimal("0.1E#{EXPONENT_MAX}")
501+
small_x = BigDecimal("0.1E#{EXPONENT_MIN}")
466502

467-
large_x = BigDecimal("10")
468-
100.times do
469-
x2 = large_x * large_x
470-
break if x2.infinite?
471-
large_x = x2
472-
end
473-
474-
small_x = BigDecimal("0.1")
475-
100.times do
476-
x2 = small_x * small_x
477-
break if x2.zero?
478-
small_x = x2
479-
end
480-
503+
assert_positive_infinite(large_x * 10)
504+
assert_positive_infinite(10 * large_x)
481505
assert_positive_infinite(large_x * large_x)
482506
assert_negative_infinite(large_x * (-large_x))
483507
assert_negative_infinite((-large_x) * large_x)
484508
assert_positive_infinite((-large_x) * (-large_x))
485509

510+
assert_positive_zero(small_x * 0.1)
511+
assert_positive_zero(0.1 * small_x)
486512
assert_positive_zero(small_x * small_x)
487513
assert_negative_zero(small_x * (-small_x))
488514
assert_negative_zero((-small_x) * small_x)
489515
assert_positive_zero((-small_x) * (-small_x))
490516

517+
assert_positive_infinite(large_x.div(0.1, 10))
491518
assert_positive_infinite(large_x.div(small_x, 10))
492519
assert_negative_infinite(large_x.div(-small_x, 10))
493520
assert_negative_infinite((-large_x).div(small_x, 10))
494521
assert_positive_infinite((-large_x).div(-small_x, 10))
495522

523+
assert_positive_zero(small_x.div(10, 10))
496524
assert_positive_zero(small_x.div(large_x, 10))
497525
assert_negative_zero(small_x.div(-large_x, 10))
498526
assert_negative_zero((-small_x).div(large_x, 10))
@@ -1567,9 +1595,9 @@ def test_inspect
15671595
assert_equal("0.123456789012e0", BigDecimal("0.123456789012").inspect)
15681596
assert_equal("0.123456789012e4", BigDecimal("1234.56789012").inspect)
15691597
assert_equal("0.123456789012e-4", BigDecimal("0.0000123456789012").inspect)
1570-
s = '-0.123456789e-1000000000000000008'
1571-
x = BigDecimal(s)
1572-
assert_equal(s, x.inspect) unless x.infinite?
1598+
# Frac part is fully packed, exponent is minimum multiple of BASE_FIG
1599+
s = "-0.#{'1' * BASE_FIG}e#{(EXPONENT_MIN / BASE_FIG + 1) * BASE_FIG}"
1600+
assert_equal(s, BigDecimal(s).inspect)
15731601
end
15741602

15751603
def test_power

0 commit comments

Comments
 (0)