1 /* 2 * Double-precision log(1+x) function. 3 * 4 * Copyright (c) 2022-2023, Arm Limited. 5 * SPDX-License-Identifier: MIT OR Apache-2.0 WITH LLVM-exception 6 */ 7 8 #include "estrin.h" 9 #include "math_config.h" 10 #include "pl_sig.h" 11 #include "pl_test.h" 12 13 #define Ln2Hi 0x1.62e42fefa3800p-1 14 #define Ln2Lo 0x1.ef35793c76730p-45 15 #define HfRt2Top 0x3fe6a09e /* top32(asuint64(sqrt(2)/2)). */ 16 #define OneMHfRt2Top \ 17 0x00095f62 /* top32(asuint64(1)) - top32(asuint64(sqrt(2)/2)). */ 18 #define OneTop12 0x3ff 19 #define BottomMask 0xffffffff 20 #define OneMHfRt2 0x3fd2bec333018866 21 #define Rt2MOne 0x3fda827999fcef32 22 #define AbsMask 0x7fffffffffffffff 23 #define ExpM63 0x3c00 24 #define C(i) __log1p_data.coeffs[i] 25 26 static inline double 27 eval_poly (double f) 28 { 29 double f2 = f * f; 30 double f4 = f2 * f2; 31 double f8 = f4 * f4; 32 return ESTRIN_18 (f, f2, f4, f8, f8 * f8, C); 33 } 34 35 /* log1p approximation using polynomial on reduced interval. Largest 36 observed errors are near the lower boundary of the region where k 37 is 0. 38 Maximum measured error: 1.75ULP. 39 log1p(-0x1.2e1aea97b3e5cp-2) got -0x1.65fb8659a2f9p-2 40 want -0x1.65fb8659a2f92p-2. */ 41 double 42 log1p (double x) 43 { 44 uint64_t ix = asuint64 (x); 45 uint64_t ia = ix & AbsMask; 46 uint32_t ia16 = ia >> 48; 47 48 /* Handle special cases first. */ 49 if (unlikely (ia16 >= 0x7ff0 || ix >= 0xbff0000000000000 50 || ix == 0x8000000000000000)) 51 { 52 if (ix == 0x8000000000000000 || ix == 0x7ff0000000000000) 53 { 54 /* x == -0 => log1p(x) = -0. 55 x == Inf => log1p(x) = Inf. */ 56 return x; 57 } 58 if (ix == 0xbff0000000000000) 59 { 60 /* x == -1 => log1p(x) = -Inf. */ 61 return __math_divzero (-1); 62 ; 63 } 64 if (ia16 >= 0x7ff0) 65 { 66 /* x == +/-NaN => log1p(x) = NaN. */ 67 return __math_invalid (asdouble (ia)); 68 } 69 /* x < -1 => log1p(x) = NaN. 70 x == -Inf => log1p(x) = NaN. */ 71 return __math_invalid (x); 72 } 73 74 /* With x + 1 = t * 2^k (where t = f + 1 and k is chosen such that f 75 is in [sqrt(2)/2, sqrt(2)]): 76 log1p(x) = k*log(2) + log1p(f). 77 78 f may not be representable exactly, so we need a correction term: 79 let m = round(1 + x), c = (1 + x) - m. 80 c << m: at very small x, log1p(x) ~ x, hence: 81 log(1+x) - log(m) ~ c/m. 82 83 We therefore calculate log1p(x) by k*log2 + log1p(f) + c/m. */ 84 85 uint64_t sign = ix & ~AbsMask; 86 if (ia <= OneMHfRt2 || (!sign && ia <= Rt2MOne)) 87 { 88 if (unlikely (ia16 <= ExpM63)) 89 { 90 /* If exponent of x <= -63 then shortcut the polynomial and avoid 91 underflow by just returning x, which is exactly rounded in this 92 region. */ 93 return x; 94 } 95 /* If x is in [sqrt(2)/2 - 1, sqrt(2) - 1] then we can shortcut all the 96 logic below, as k = 0 and f = x and therefore representable exactly. 97 All we need is to return the polynomial. */ 98 return fma (x, eval_poly (x) * x, x); 99 } 100 101 /* Obtain correctly scaled k by manipulation in the exponent. */ 102 double m = x + 1; 103 uint64_t mi = asuint64 (m); 104 uint32_t u = (mi >> 32) + OneMHfRt2Top; 105 int32_t k = (int32_t) (u >> 20) - OneTop12; 106 107 /* Correction term c/m. */ 108 double cm = (x - (m - 1)) / m; 109 110 /* Reduce x to f in [sqrt(2)/2, sqrt(2)]. */ 111 uint32_t utop = (u & 0x000fffff) + HfRt2Top; 112 uint64_t u_red = ((uint64_t) utop << 32) | (mi & BottomMask); 113 double f = asdouble (u_red) - 1; 114 115 /* Approximate log1p(x) on the reduced input using a polynomial. Because 116 log1p(0)=0 we choose an approximation of the form: 117 x + C0*x^2 + C1*x^3 + C2x^4 + ... 118 Hence approximation has the form f + f^2 * P(f) 119 where P(x) = C0 + C1*x + C2x^2 + ... */ 120 double p = fma (f, eval_poly (f) * f, f); 121 122 double kd = k; 123 double y = fma (Ln2Lo, kd, cm); 124 return y + fma (Ln2Hi, kd, p); 125 } 126 127 PL_SIG (S, D, 1, log1p, -0.9, 10.0) 128 PL_TEST_ULP (log1p, 1.26) 129 PL_TEST_INTERVAL (log1p, -10.0, 10.0, 10000) 130 PL_TEST_INTERVAL (log1p, 0.0, 0x1p-23, 50000) 131 PL_TEST_INTERVAL (log1p, 0x1p-23, 0.001, 50000) 132 PL_TEST_INTERVAL (log1p, 0.001, 1.0, 50000) 133 PL_TEST_INTERVAL (log1p, 0.0, -0x1p-23, 50000) 134 PL_TEST_INTERVAL (log1p, -0x1p-23, -0.001, 50000) 135 PL_TEST_INTERVAL (log1p, -0.001, -1.0, 50000) 136 PL_TEST_INTERVAL (log1p, -1.0, inf, 5000) 137