Skip to content

Commit 9df6e59

Browse files
committed
Improve performance of add/sub when exponent of two bigdecimals have huge difference
BigDecimal('1e+10000000000').add(1, 10) allocates GB of memory. BigDecimal('1e+10000000000').add(BigDecimal('0.1e+9999999990'), 10) will give the same rounding result with less memory allocation and computation time.
1 parent 1350fa5 commit 9df6e59

File tree

2 files changed

+69
-4
lines changed

2 files changed

+69
-4
lines changed

ext/bigdecimal/bigdecimal.c

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -986,7 +986,7 @@ BigDecimal_mode(int argc, VALUE *argv, VALUE self)
986986
static size_t
987987
GetAddSubPrec(Real *a, Real *b)
988988
{
989-
if (!VpIsDef(a) || !VpIsDef(b)) return (size_t)-1L;
989+
if (VpIsZero(a) || VpIsZero(b)) return Max(a->Prec, b->Prec);
990990
ssize_t min_a = a->exponent - a->Prec;
991991
ssize_t min_b = b->exponent - b->Prec;
992992
return Max(a->exponent, b->exponent) - Min(min_a, min_b);
@@ -1290,13 +1290,32 @@ BigDecimal_addsub_with_coerce(VALUE self, VALUE r, size_t prec, int operation)
12901290
if (VpIsNaN(a.real)) return CheckGetValue(a);
12911291
if (VpIsNaN(b.real)) return CheckGetValue(b);
12921292

1293-
mx = GetAddSubPrec(a.real, b.real);
1294-
if (mx == (size_t)-1L) {
1295-
/* a or b is inf */
1293+
if (VpIsInf(a.real) || VpIsInf(b.real)) {
12961294
c = NewZeroWrap(1, BASE_FIG);
12971295
VpAddSub(c.real, a.real, b.real, operation);
12981296
}
12991297
else {
1298+
1299+
// Optimization when exponent difference is large
1300+
// (1.234e+1000).add(5.678e-1000, 10) == (1.234e+1000).add(0.1e+990, 10) in every rounding mode
1301+
if (prec && !VpIsZero(a.real) && !VpIsZero(b.real)) {
1302+
size_t precRoom = roomof(prec, BASE_FIG);
1303+
if (a.real->exponent - (ssize_t)Max(a.real->Prec, precRoom) - 1 > b.real->exponent) {
1304+
BDVALUE b2 = NewZeroWrap(1, BASE_FIG);
1305+
VpSetOne(b2.real)
1306+
VpSetSign(b2.real, b.real->sign);
1307+
b2.real->exponent = a.real->exponent - (ssize_t)Max(a.real->Prec, precRoom) - 1;
1308+
b = b2;
1309+
} else if (b.real->exponent - (ssize_t)Max(b.real->Prec, precRoom) - 1 > a.real->exponent) {
1310+
BDVALUE a2 = NewZeroWrap(1, BASE_FIG);
1311+
VpSetOne(a2.real)
1312+
VpSetSign(a2.real, a.real->sign);
1313+
a2.real->exponent = b.real->exponent - (ssize_t)Max(b.real->Prec, precRoom) - 1;
1314+
a = a2;
1315+
}
1316+
}
1317+
1318+
mx = GetAddSubPrec(a.real, b.real);
13001319
c = NewZeroWrap(1, (mx + 1) * BASE_FIG);
13011320
size_t pl = VpGetPrecLimit();
13021321
if (prec) VpSetPrecLimit(prec);

test/bigdecimal/test_bigdecimal.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,52 @@ def test_add_sub_underflow
523523
assert_negative_zero((-x) - (-y))
524524
end
525525

526+
def test_add_sub_huge_with_zero
527+
huge = BigDecimal("0.1e#{EXPONENT_MAX}")
528+
assert_equal(huge, huge + 0)
529+
assert_equal(huge, huge - 0)
530+
assert_equal(huge, huge.add(BigDecimal(0), 10))
531+
assert_equal(huge, huge.sub(BigDecimal(0), 10))
532+
end
533+
534+
def test_add_sub_huge_exponent_difference
535+
huge = BigDecimal('1e+1000000000000')
536+
zero = BigDecimal(0)
537+
small = BigDecimal('1e-1000000000000')
538+
assert_equal(huge, huge.add(small, 10))
539+
assert_equal(huge, huge.sub(small, 10))
540+
assert_equal(huge, small.add(huge, 10))
541+
assert_equal(-huge, small.sub(huge, 10))
542+
assert_equal(huge, zero.add(huge, 10))
543+
assert_equal(huge, huge.add(zero, 10))
544+
assert_equal(small, zero.add(small, 10))
545+
assert_equal(small, small.add(zero, 10))
546+
547+
BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_DOWN)
548+
assert_equal(huge, huge.add(small, 10))
549+
assert_equal(huge * BigDecimal('0.9999999999'), huge.sub(small, 10))
550+
assert_equal(huge, huge.add(zero, 10))
551+
assert_equal(huge, huge.sub(zero, 10))
552+
553+
BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_UP)
554+
assert_equal(huge * BigDecimal('1.000000001'), huge.add(small, 10))
555+
assert_equal(huge, huge.sub(small, 10))
556+
assert_equal(huge, huge.add(zero, 10))
557+
assert_equal(huge, huge.sub(zero, 10))
558+
559+
[BigDecimal::ROUND_UP, BigDecimal::ROUND_DOWN, BigDecimal::ROUND_HALF_UP].each do |mode|
560+
BigDecimal.mode(BigDecimal::ROUND_MODE, mode)
561+
xs = [BigDecimal(1), BigDecimal(2)]
562+
ys = [BigDecimal('0.5e-89'), BigDecimal('0.5e-90'), BigDecimal('0.5e-91')]
563+
xs.product(ys).each do |x, y|
564+
assert_equal((x + y).mult(1, 91), x.add(y, 91))
565+
assert_equal((y + x).mult(1, 91), y.add(x, 91))
566+
assert_equal((x - y).mult(1, 91), x.sub(y, 91))
567+
assert_equal((y - x).mult(1, 91), y.sub(x, 91))
568+
end
569+
end
570+
end
571+
526572
def test_mult_div_overflow_underflow_sign
527573
BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false)
528574
BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, false)

0 commit comments

Comments
 (0)