1 /*
2  * Single-precision vector 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 "v_math.h"
9 #include "pl_sig.h"
10 #include "pl_test.h"
11 
12 #if V_SUPPORTED
13 
14 #define AbsMask 0x7fffffff
15 #define TinyBound 0x340 /* asuint32(0x1p-23). ulp=0.5 at 0x1p-23.  */
16 #define MinusOne 0xbf800000
17 #define Ln2 (0x1.62e43p-1f)
18 #define Four 0x40800000
19 #define ThreeQuarters v_u32 (0x3f400000)
20 
21 #define C(i) v_f32 (__log1pf_data.coeffs[i])
22 
23 static inline v_f32_t
24 eval_poly (v_f32_t m)
25 {
26 #ifdef V_LOG1PF_1U3
27 
28   /* Approximate log(1+m) on [-0.25, 0.5] using Horner scheme.  */
29   v_f32_t p = v_fma_f32 (C (8), m, C (7));
30   p = v_fma_f32 (p, m, C (6));
31   p = v_fma_f32 (p, m, C (5));
32   p = v_fma_f32 (p, m, C (4));
33   p = v_fma_f32 (p, m, C (3));
34   p = v_fma_f32 (p, m, C (2));
35   p = v_fma_f32 (p, m, C (1));
36   p = v_fma_f32 (p, m, C (0));
37   return v_fma_f32 (m, m * p, m);
38 
39 #elif defined(V_LOG1PF_2U5)
40 
41   /* Approximate log(1+m) on [-0.25, 0.5] using Estrin scheme.  */
42   v_f32_t p_12 = v_fma_f32 (m, C (1), C (0));
43   v_f32_t p_34 = v_fma_f32 (m, C (3), C (2));
44   v_f32_t p_56 = v_fma_f32 (m, C (5), C (4));
45   v_f32_t p_78 = v_fma_f32 (m, C (7), C (6));
46 
47   v_f32_t m2 = m * m;
48   v_f32_t p_02 = v_fma_f32 (m2, p_12, m);
49   v_f32_t p_36 = v_fma_f32 (m2, p_56, p_34);
50   v_f32_t p_79 = v_fma_f32 (m2, C (8), p_78);
51 
52   v_f32_t m4 = m2 * m2;
53   v_f32_t p_06 = v_fma_f32 (m4, p_36, p_02);
54 
55   return v_fma_f32 (m4, m4 * p_79, p_06);
56 
57 #else
58 #error No precision specified for v_log1pf
59 #endif
60 }
61 
62 static inline float
63 handle_special (float x)
64 {
65   uint32_t ix = asuint (x);
66   uint32_t ia = ix & AbsMask;
67   if (ix == 0xff800000 || ia > 0x7f800000 || ix > 0xbf800000)
68     {
69       /* x == -Inf   => log1pf(x) = NaN.
70 	 x <  -1.0   => log1pf(x) = NaN.
71 	 x == +/-NaN => log1pf(x) = NaN.  */
72 #if WANT_SIMD_EXCEPT
73       return __math_invalidf (asfloat (ia));
74 #else
75       return NAN;
76 #endif
77     }
78   if (ix == 0xbf800000)
79     {
80       /* x == -1.0 => log1pf(x) = -Inf.  */
81 #if WANT_SIMD_EXCEPT
82       return __math_divzerof (ix);
83 #else
84       return -INFINITY;
85 #endif
86     }
87   /* |x| < TinyBound => log1p(x)  =  x.  */
88   return x;
89 }
90 
91 /* Vector log1pf approximation using polynomial on reduced interval. Accuracy is
92    the same as for the scalar algorithm, i.e. worst-case error when using Estrin
93    is roughly 2.02 ULP:
94    log1pf(0x1.21e13ap-2) got 0x1.fe8028p-3 want 0x1.fe802cp-3.  */
95 VPCS_ATTR v_f32_t V_NAME (log1pf) (v_f32_t x)
96 {
97   v_u32_t ix = v_as_u32_f32 (x);
98   v_u32_t ia12 = (ix >> 20) & v_u32 (0x7f8);
99   v_u32_t special_cases
100     = v_cond_u32 (ia12 - v_u32 (TinyBound) >= (0x7f8 - TinyBound))
101       | v_cond_u32 (ix >= MinusOne);
102   v_f32_t special_arg = x;
103 
104 #if WANT_SIMD_EXCEPT
105   if (unlikely (v_any_u32 (special_cases)))
106     /* Side-step special lanes so fenv exceptions are not triggered
107        inadvertently.  */
108     x = v_sel_f32 (special_cases, v_f32 (1), x);
109 #endif
110 
111   /* With x + 1 = t * 2^k (where t = m + 1 and k is chosen such that m
112 			   is in [-0.25, 0.5]):
113      log1p(x) = log(t) + log(2^k) = log1p(m) + k*log(2).
114 
115      We approximate log1p(m) with a polynomial, then scale by
116      k*log(2). Instead of doing this directly, we use an intermediate
117      scale factor s = 4*k*log(2) to ensure the scale is representable
118      as a normalised fp32 number.  */
119 
120   v_f32_t m = x + v_f32 (1.0f);
121 
122   /* Choose k to scale x to the range [-1/4, 1/2].  */
123   v_s32_t k = (v_as_s32_f32 (m) - ThreeQuarters) & v_u32 (0xff800000);
124 
125   /* Scale x by exponent manipulation.  */
126   v_f32_t m_scale = v_as_f32_u32 (v_as_u32_f32 (x) - v_as_u32_s32 (k));
127 
128   /* Scale up to ensure that the scale factor is representable as normalised
129      fp32 number, and scale m down accordingly.  */
130   v_f32_t s = v_as_f32_u32 (v_u32 (Four) - k);
131   m_scale = m_scale + v_fma_f32 (v_f32 (0.25f), s, v_f32 (-1.0f));
132 
133   /* Evaluate polynomial on the reduced interval.  */
134   v_f32_t p = eval_poly (m_scale);
135 
136   /* The scale factor to be applied back at the end - by multiplying float(k)
137      by 2^-23 we get the unbiased exponent of k.  */
138   v_f32_t scale_back = v_to_f32_s32 (k) * v_f32 (0x1p-23f);
139 
140   /* Apply the scaling back.  */
141   v_f32_t y = v_fma_f32 (scale_back, v_f32 (Ln2), p);
142 
143   if (unlikely (v_any_u32 (special_cases)))
144     return v_call_f32 (handle_special, special_arg, y, special_cases);
145   return y;
146 }
147 VPCS_ALIAS
148 
149 PL_SIG (V, F, 1, log1p, -0.9, 10.0)
150 PL_TEST_ULP (V_NAME (log1pf), 1.53)
151 PL_TEST_EXPECT_FENV (V_NAME (log1pf), WANT_SIMD_EXCEPT)
152 PL_TEST_INTERVAL (V_NAME (log1pf), -10.0, 10.0, 10000)
153 PL_TEST_INTERVAL (V_NAME (log1pf), 0.0, 0x1p-23, 30000)
154 PL_TEST_INTERVAL (V_NAME (log1pf), 0x1p-23, 0.001, 50000)
155 PL_TEST_INTERVAL (V_NAME (log1pf), 0.001, 1.0, 50000)
156 PL_TEST_INTERVAL (V_NAME (log1pf), 0.0, -0x1p-23, 30000)
157 PL_TEST_INTERVAL (V_NAME (log1pf), -0x1p-23, -0.001, 30000)
158 PL_TEST_INTERVAL (V_NAME (log1pf), -0.001, -1.0, 50000)
159 PL_TEST_INTERVAL (V_NAME (log1pf), -1.0, inf, 1000)
160 #endif
161