diff --git a/include/lol/private/types/real.h b/include/lol/private/types/real.h index a9aa6221..f262efb7 100644 --- a/include/lol/private/types/real.h +++ b/include/lol/private/types/real.h @@ -120,10 +120,12 @@ public: // Exponential and logarithmic functions template friend real_t exp(real_t const &x); template friend real_t exp2(real_t const &x); + template friend real_t expm1(real_t const &x); template friend real_t erf(real_t const &x); template friend real_t log(real_t const &x); template friend real_t log2(real_t const &x); template friend real_t log10(real_t const &x); + template friend real_t log1p(real_t const &x); // Floating-point functions template friend real_t frexp(real_t const &x, diff --git a/include/lol/private/types/real.ipp b/include/lol/private/types/real.ipp index 40b2abfa..c2f46d3c 100644 --- a/include/lol/private/types/real.ipp +++ b/include/lol/private/types/real.ipp @@ -1012,6 +1012,29 @@ template int sign(real_t const &x) return x.is_zero() ? 0 : x.is_negative() ? -1 : 1; } +template real_t fast_log_1p_1m(real_t const &x) +{ + // This is a fast implementation of ln(y) where y = (x + 1) / (x - 1): + // ln(y) = ln(1 + x) - ln(1 - x) + // = x - x²/2 + x³/3 - x⁴/4 … + x - x²/2 + x³/3 - x⁴/4 … + // = 2 (x + x³/3 + x⁵/5 …) + // = 2 x (1 + x²/3 + x⁴/5 + x⁶/7 …) + // Convergence is fast for values near 0. + auto x2 = x * x, xn = x2; + auto sum = real_t::R_1(); + + for (int i = 3; ; i += 2) + { + auto newsum = sum + xn / real_t(i); + if (newsum == sum) + break; + sum = newsum; + xn *= x2; + } + + return x * sum * 2; +} + template real_t fast_log(real_t const &x) { /* This fast log method is tuned to work on the [1..2] range and @@ -1024,25 +1047,14 @@ template real_t fast_log(real_t const &x) * And the following identities: * ln(x) = 2 ln(y) * = 2 ln((1 + z) / (1 - z)) - * = 4 z (1 + z^2 / 3 + z^4 / 5 + z^6 / 7...) * * Any additional sqrt() call would halve the convergence time, but * would also impact the final precision. For now we stick with one * sqrt() call. */ auto y = sqrt(x); - auto z = (y - real_t::R_1()) / (y + real_t::R_1()), z2 = z * z, zn = z2; - auto sum = real_t::R_1(); - - for (int i = 3; ; i += 2) - { - auto newsum = sum + zn / real_t(i); - if (newsum == sum) - break; - sum = newsum; - zn *= z2; - } + auto z = (y - real_t::R_1()) / (y + real_t::R_1()); - return z * sum * 4; + return fast_log_1p_1m(z) * 2; } template real_t log(real_t const &x) @@ -1057,6 +1069,20 @@ template real_t log(real_t const &x) return real_t(x.m_exponent) * real_t::R_LN2() + fast_log(tmp); } +template real_t log1p(real_t const &x) +{ + // Strategy for log1p(x): if |x| < 0.5 then we use the following + // variable substitution: + // y = x / (2 + x) + // + // And the following identity: + // ln(1 + x) = ln((1 + y) / ( 1 - y)) + // + // Otherwise we just use standard log() + bool near_zero = (fabs(x) < real_t::R_1() / 2); + return near_zero ? fast_log_1p_1m(x / (real_t::R_2() + x)) : log(x) - real_t::R_1(); +} + template real_t log2(real_t const &x) { /* Strategy for log2(x): see log(x). */ @@ -1129,6 +1155,12 @@ template real_t exp2(real_t const &x) return x1; } +template real_t expm1(real_t const &x) +{ + bool near_zero = (fabs(x) < real_t::R_1() / 2); + return near_zero ? fast_exp_sub(x, real_t::R_1()) : exp(x) - real_t::R_1(); +} + template real_t erf(real_t const &x) { /* Strategy for erf(x):