Skip to content

Conversation

@tompng
Copy link
Member

@tompng tompng commented Jun 21, 2025

Followup of #336

Calculates erf and erfc in Taylor series or in asymptotic expansion.

Taylor series for small x and Asymptotic expansion for large x

Asymptotic expansion is generally faster, but has some restriction.
So first try asymptotic expansion and then fallback to Taylor series.

Asymptotic expansion

See https://en.wikipedia.org/wiki/Error_function#Asymptotic_expansion

For example: Asymptotic expansion can calculate erfc(10) in maximum 42 digits.
erfc_with_asymptotic_expansion(10) = 0.208848758376254475700078629495778861156082e-44

BigMath.erf(10, 50) can be calculated as 1 - BigMath.send(:_erfc_asymptotic, BigDecimal(10), 50 - 44)
On the other hand, calculating BigMath.erf(10, 100) as 1 - BigMath.send(:_erfc_asymptotic, BigDecimal(10), 100 - 44) fails because maximum digits for erfc(10) is 42 but this calculation requires more digits.
In this case, we need to fallbacks to Taylor series.

Example benchmark:

erfc(50, 50) #=> 0.20709207788416560484484478751657887929322509209954e-1087
# in asymptotic expansion: 0.000667 seconds
1 - erf(50, 50 + (extra_digits=1087))
# in taylor series:        0.079756 seconds

Taylor series

Use Taylor series of exp(x**2)*erf(x).
Precision management is easier than normal Taylor series of erf(x).

# Taylor series of erf(x)
# Sign of the coefficients alternates. Considering loss of significance, extra high precision is required.
# Estimation of extra digits is also required.
erf(x) = (2/√π) * (x - x**3/3 + x**5/10 - x**7/42 + x**9/216 - ...)
# Taylor series of exp(x**2)*erf(x)
# Coefficients are all positive. `coef[k]*x**k` needs `prec` digits.
erf(x) = (2/√π) * exp(-x**2) * (x + 2*x**3/3 + 4*x**5/15 + 8*x**7/105 + 16*x**9/945 + ...)

Faster calculation

Calculating Taylor series of erf(large_x) is slow.
It can be calculated faster by two steps of Taylor series calculation.

x = 123.456789
a = 123.4
# Converge is slow, but a.n_significant_digit is small, so calculation of a**k is fast.
erf_a = erf_taylor_around_zero(a, prec)
# Converge is fast because x-a is small. Calculation of (x-a)**k is not so fast.
erf_taylor_around_a(x, a, erf_a, prec)

Example benchmark:

BigMath.erf(20 + BigDecimal(2).sqrt(1000), 1000)
# Taylor series around zero: 0.552874 seconds
# Two step Taylor series:    0.055574 seconds

@tompng tompng force-pushed the bigmath_erf branch 2 times, most recently from 11c65f8 to 02e22d4 Compare June 21, 2025 12:55
@tompng tompng force-pushed the bigmath_erf branch 2 times, most recently from 98d63f9 to 088978c Compare August 10, 2025 06:32
@tompng tompng force-pushed the bigmath_erf branch 2 times, most recently from 72f349c to c885098 Compare September 12, 2025 19:54
@tompng tompng marked this pull request as ready for review October 29, 2025 12:02
@tompng tompng force-pushed the bigmath_erf branch 2 times, most recently from 0f821ee to 2441bcb Compare November 5, 2025 06:35
@tompng tompng force-pushed the bigmath_erf branch 2 times, most recently from a05a403 to 75582a4 Compare November 13, 2025 17:49
Uses asymptotic expansion of erfc if possible and fallback to taylor series of erf
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.

1 participant