Improve taylor series calculation of exp and sin by binary splitting method
With #407, exp(x, N) and sin(x, N) calculation improves from O(N^3/logN) to O(N^2).
This pull request further improves O(N^2) to O(N*(logN)**a) where a is about 3 or 4.
BigMath.exp(1, 10_000_000) # 13.146789s
BigMath.exp(BigDecimal(2).sqrt(10_000_000), 10_000_000) # 167.920334s
Drops Ruby 2.5 support to use (1..).bsearch
Binary Splitting method
If x can be represented as small_numerator/small_denominator, exp(x) and sin(x) can be calculated fast using binary splitting method.
This technique is normally used in PI calculation.
Here's an example of calculating exp(1).
exp(1) = 1 + 1/1 * (1 + 1/2 * (1 + 1/3 * (1 + 1/4 * (1 + 1/5 * (1 + 1/6 * (1 + 1/7 * (1 + 1/8 * (1 + rest))))))))
= 2 + 1/2 * (4/3 + 1/12 * (6/5 + 1/30 * (8/7 + 1/56 * (1 + rest))))
= 8/3 + 1/24 * (26/21 + 1/1680 * (1 + rest))
= 109601/40320 + 1/40320 * rest
The prerequisite is that all numbers are small in the initial step. In each step, average number of digits doubles and number of terms halves.
Split digits (decimal form of bit-burst algorithm)
Normally, x has many digits, so simply applying binary splitting method to exp(x) isn't fast.
Now lets split x into several parts.
exp(x.xxxxxxxxxxxxxxxx) = exp(x.xx) * exp(0.00xx) * exp(0.0000xxxx) * exp(0.00000000xxxxxxxx)
Number of digits doubled, number of terms required for convergence is almost halved.
exp(0.00xx, 1000) # digits: 2, terms: 253
exp(0.0000xxxx, 1000) # digits: 4, terms: 173
exp(0.00000000xxxxxxxx, 1000) # digits: 8, terms: 105
exp(0.0000000000000000x..., 1000) # digits: 16, terms: 58
So each exp calculation performs well with binary splitting method.
We need to repeat binary splitting method for about logN times, so the total time complexity will be quasilinear time.
BigMath.sin
Almost the same as BigMath.exp.
Restore sin(x.xxxxxxxxxxxxxxxx) from sin(x.xx), sin(0.00xx), sin(0.0000xxxx), sin(0.00000000xxxxxxxx) using trigonometric addition theorem.
BigMath.log and BigMath.atan
Since exp and sin becomes orders of magnitude faster, implementing log and atan by newton's method is better.
log(x) : Solve exp(y) - x = 0
atan(x) : Solve tan(y) - x = 0
There is an option to implement binary splitting method to log and atan taylor series, but implementation will be more complicated, needs special care to make |x| ≪ 1, and convergence is slower than exp and sin.