xref: /freebsd/contrib/arm-optimized-routines/pl/math/log1p_2u.c (revision 5e3190f700637fcfc1a52daeaa4a031fdd2557c7)
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