Skip to content

Commit 27f0242

Browse files
authored
Improve performance of add/sub when exponent of two bigdecimals have huge difference (#478)
* 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. * Remove workaround of add/sub performance bug
1 parent 1350fa5 commit 27f0242

3 files changed

Lines changed: 71 additions & 18 deletions

File tree

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);

lib/bigdecimal/math.rb

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -574,14 +574,7 @@ def expm1(x, prec)
574574

575575
return BigDecimal(-1) if exp_prec <= 0
576576

577-
exp = exp(x, exp_prec)
578-
579-
if exp.exponent > prec + BigDecimal.double_fig
580-
# Workaroudn for https://github.com/ruby/bigdecimal/issues/464
581-
exp
582-
else
583-
exp.sub(1, prec)
584-
end
577+
exp(x, exp_prec).sub(1, prec)
585578
end
586579

587580
# erf(decimal, numeric) -> BigDecimal
@@ -608,12 +601,7 @@ def erf(x, prec)
608601
log10_erfc = -xf ** 2 / Math.log(10) - Math.log10(xf * Math::PI ** 0.5)
609602
erfc_prec = [prec + log10_erfc.ceil, 1].max
610603
erfc = _erfc_asymptotic(x, erfc_prec)
611-
if erfc
612-
# Workaround for https://github.com/ruby/bigdecimal/issues/464
613-
return BigDecimal(1) if erfc.exponent < -prec - BigDecimal.double_fig
614-
615-
return BigDecimal(1).sub(erfc, prec)
616-
end
604+
return BigDecimal(1).sub(erfc, prec) if erfc
617605
end
618606

619607
prec2 = prec + BigDecimal.double_fig

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)