Skip to content

Conversation

@tompng
Copy link
Member

@tompng tompng commented Jun 8, 2025

Writing in Ruby is easier than C.
This will bring both maintainability and performance (because implementing faster algorithm is easy).
Eliminates problem like #340 in the long run. (already fixed)
Use case of BigMath.log BigMath.exp are high-precision calculation (if not, using Float is enough).
Cost of multiplications/divisions are dominant. There is not much need to implement it in C.

Fixes #83, #345, #352
Fixes power, log and exp part of #340 (already fixed)

Benchmark

Makes BigMath.log(10, prec) which is the bottleneck of BigDecimal#** faster

scenario BigMath.log(10, 1000) BigMath.log(10, 30)
Master 0.644637s 0.000409s
This pull request 0.044400s 0.000072s
With #343 0.007985s 0.000071s
With #323 0.004952s 0.000061s

Makes BigMath.exp(large_x) faster (#82)

scenario BigMath.exp(10000, 100) BigMath.exp(100000, 100)
Master 0.011882s 0.366918s
This pull request 0.000192s 0.000211s

@tompng tompng force-pushed the exp_log_pow_in_ruby branch 5 times, most recently from 21e90c5 to 5c4d747 Compare June 11, 2025 20:28
@tompng tompng force-pushed the exp_log_pow_in_ruby branch 3 times, most recently from 674e7f1 to b3212f4 Compare June 13, 2025 16:03
@tompng tompng marked this pull request as ready for review June 13, 2025 16:07
Comment on lines 96 to 99
# Detect overflow/underflow before consuming infinite memory
if (xn.exponent.abs - 1) * int_part / n > 1000000000000000000
return ((xn.exponent > 0) ^ neg ? BigDecimal::INFINITY : BigDecimal(0)) * (int_part.even? || x > 0 ? 1 : -1)
end
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is tested as a spec in test_bigdecimal.rb
assert_positive_infinite(BigDecimal(3) ** (2**100))


# exp(x * 10**cnt) = exp(x)**(10**cnt)
cnt = x > 1 ? x.exponent : 0
prec2 = prec + BigDecimal.double_fig + cnt
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

y**10 makes relative error 10 times larger. (y*(1+e))**10 = y**10 * (1+10*e+...)
In the last step of this method cnt.times{y = y**10}, error will be 10**cnt times larger.
Calculation of exp(x) needs cnt more digits.


# Taylor series for log(x) around 1
# log(x) = -log((1 + X) / (1 - X)) where X = (x - 1) / (x + 1)
# log(x) = 2 * (X + X**3 / 3 + X**5 / 5 + X**7 / 7 + ...)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what the original log implementation in C was doing.
Converts x: 0.1..10(x-1)/(x+1): -0.818..0.818 to make Taylor series converge. (but converge of 0.818**n was very slow. This is the slowness reason of log(10))

prec += BigDecimal.double_fig

# log(x) = log(sqrt(sqrt(sqrt(sqrt(x))))) * 2**sqrt_steps
sqrt_steps = [2 * Integer.sqrt(prec) + 3 * x_minus_one_exponent, 0].max
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To minimize the computation cost, we need to minimize this:

sqrt_cost * sqrt_steps + mult_cost * taylor_steps

Taylor steps can be approximated as:

taylor_steps = const1 * prec / (-log10(|x-1| / 2**sqrt_steps))

Assuming sqrt_cost and mult_cost are ideally the same order, (note that currently it is not)
We need to minimize something like this:

sqrt_steps + const1 * prec / (log10(|x-1|) + log10(2) * sqrt_step)

sqrt_steps that minimizes the cost should be

sqrt_steps = const2 * sqrt(prec) + log10(|x-1|) / log10(2)
           ≒ const2 * sqrt(prec) + 3 * x_minus_one_exponent

@mrkn mrkn added this to the v3.3 milestone Jun 17, 2025
@mrkn mrkn force-pushed the exp_log_pow_in_ruby branch from 5f90e04 to 94deded Compare June 18, 2025 02:03
@tompng tompng force-pushed the exp_log_pow_in_ruby branch from 94deded to dc66026 Compare July 6, 2025 05:41
@tompng tompng mentioned this pull request Jul 6, 2025
@tompng tompng force-pushed the exp_log_pow_in_ruby branch from dc66026 to 29c15a5 Compare July 14, 2025 13:30
@tompng tompng force-pushed the exp_log_pow_in_ruby branch from 29c15a5 to 00aee7c Compare July 18, 2025 15:52
@mrkn mrkn merged commit 0d854c4 into ruby:master Jul 19, 2025
79 checks passed
@tompng tompng deleted the exp_log_pow_in_ruby branch July 19, 2025 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support large integral exponent in BigDecimal#power and BigDecimal#**

2 participants