129866ecbSAndrew Turner /*
229866ecbSAndrew Turner * Single-precision log(1+x) function.
329866ecbSAndrew Turner *
49d1de259SAndrew Turner * Copyright (c) 2022-2024, Arm Limited.
529866ecbSAndrew Turner * SPDX-License-Identifier: MIT OR Apache-2.0 WITH LLVM-exception
629866ecbSAndrew Turner */
729866ecbSAndrew Turner
8edc5c0deSAndrew Turner #include "poly_scalar_f32.h"
929866ecbSAndrew Turner #include "math_config.h"
109d1de259SAndrew Turner #include "test_sig.h"
119d1de259SAndrew Turner #include "test_defs.h"
1229866ecbSAndrew Turner
1329866ecbSAndrew Turner #define Ln2 (0x1.62e43p-1f)
1429866ecbSAndrew Turner #define SignMask (0x80000000)
1529866ecbSAndrew Turner
1629866ecbSAndrew Turner /* Biased exponent of the largest float m for which m^8 underflows. */
1729866ecbSAndrew Turner #define M8UFLOW_BOUND_BEXP 112
1829866ecbSAndrew Turner /* Biased exponent of the largest float for which we just return x. */
1929866ecbSAndrew Turner #define TINY_BOUND_BEXP 103
2029866ecbSAndrew Turner
2129866ecbSAndrew Turner #define C(i) __log1pf_data.coeffs[i]
2229866ecbSAndrew Turner
2329866ecbSAndrew Turner static inline float
eval_poly(float m,uint32_t e)2429866ecbSAndrew Turner eval_poly (float m, uint32_t e)
2529866ecbSAndrew Turner {
2629866ecbSAndrew Turner #ifdef LOG1PF_2U5
2729866ecbSAndrew Turner
2829866ecbSAndrew Turner /* 2.5 ulp variant. Approximate log(1+m) on [-0.25, 0.5] using
2929866ecbSAndrew Turner slightly modified Estrin scheme (no x^0 term, and x term is just x). */
3029866ecbSAndrew Turner float p_12 = fmaf (m, C (1), C (0));
3129866ecbSAndrew Turner float p_34 = fmaf (m, C (3), C (2));
3229866ecbSAndrew Turner float p_56 = fmaf (m, C (5), C (4));
3329866ecbSAndrew Turner float p_78 = fmaf (m, C (7), C (6));
3429866ecbSAndrew Turner
3529866ecbSAndrew Turner float m2 = m * m;
3629866ecbSAndrew Turner float p_02 = fmaf (m2, p_12, m);
3729866ecbSAndrew Turner float p_36 = fmaf (m2, p_56, p_34);
3829866ecbSAndrew Turner float p_79 = fmaf (m2, C (8), p_78);
3929866ecbSAndrew Turner
4029866ecbSAndrew Turner float m4 = m2 * m2;
4129866ecbSAndrew Turner float p_06 = fmaf (m4, p_36, p_02);
4229866ecbSAndrew Turner
4329866ecbSAndrew Turner if (unlikely (e < M8UFLOW_BOUND_BEXP))
4429866ecbSAndrew Turner return p_06;
4529866ecbSAndrew Turner
4629866ecbSAndrew Turner float m8 = m4 * m4;
4729866ecbSAndrew Turner return fmaf (m8, p_79, p_06);
4829866ecbSAndrew Turner
4929866ecbSAndrew Turner #elif defined(LOG1PF_1U3)
5029866ecbSAndrew Turner
5129866ecbSAndrew Turner /* 1.3 ulp variant. Approximate log(1+m) on [-0.25, 0.5] using Horner
5229866ecbSAndrew Turner scheme. Our polynomial approximation for log1p has the form
5329866ecbSAndrew Turner x + C1 * x^2 + C2 * x^3 + C3 * x^4 + ...
5429866ecbSAndrew Turner Hence approximation has the form m + m^2 * P(m)
5529866ecbSAndrew Turner where P(x) = C1 + C2 * x + C3 * x^2 + ... . */
56edc5c0deSAndrew Turner return fmaf (m, m * horner_8_f32 (m, __log1pf_data.coeffs), m);
5729866ecbSAndrew Turner
5829866ecbSAndrew Turner #else
5929866ecbSAndrew Turner #error No log1pf approximation exists with the requested precision. Options are 13 or 25.
6029866ecbSAndrew Turner #endif
6129866ecbSAndrew Turner }
6229866ecbSAndrew Turner
6329866ecbSAndrew Turner static inline uint32_t
biased_exponent(uint32_t ix)6429866ecbSAndrew Turner biased_exponent (uint32_t ix)
6529866ecbSAndrew Turner {
6629866ecbSAndrew Turner return (ix & 0x7f800000) >> 23;
6729866ecbSAndrew Turner }
6829866ecbSAndrew Turner
6929866ecbSAndrew Turner /* log1pf approximation using polynomial on reduced interval. Worst-case error
7029866ecbSAndrew Turner when using Estrin is roughly 2.02 ULP:
7129866ecbSAndrew Turner log1pf(0x1.21e13ap-2) got 0x1.fe8028p-3 want 0x1.fe802cp-3. */
7229866ecbSAndrew Turner float
log1pf(float x)7329866ecbSAndrew Turner log1pf (float x)
7429866ecbSAndrew Turner {
7529866ecbSAndrew Turner uint32_t ix = asuint (x);
7629866ecbSAndrew Turner uint32_t ia = ix & ~SignMask;
7729866ecbSAndrew Turner uint32_t ia12 = ia >> 20;
7829866ecbSAndrew Turner uint32_t e = biased_exponent (ix);
7929866ecbSAndrew Turner
8029866ecbSAndrew Turner /* Handle special cases first. */
8129866ecbSAndrew Turner if (unlikely (ia12 >= 0x7f8 || ix >= 0xbf800000 || ix == 0x80000000
8229866ecbSAndrew Turner || e <= TINY_BOUND_BEXP))
8329866ecbSAndrew Turner {
8429866ecbSAndrew Turner if (ix == 0xff800000)
8529866ecbSAndrew Turner {
8629866ecbSAndrew Turner /* x == -Inf => log1pf(x) = NaN. */
8729866ecbSAndrew Turner return NAN;
8829866ecbSAndrew Turner }
8929866ecbSAndrew Turner if ((ix == 0x7f800000 || e <= TINY_BOUND_BEXP) && ia12 <= 0x7f8)
9029866ecbSAndrew Turner {
9129866ecbSAndrew Turner /* |x| < TinyBound => log1p(x) = x.
9229866ecbSAndrew Turner x == Inf => log1pf(x) = Inf. */
9329866ecbSAndrew Turner return x;
9429866ecbSAndrew Turner }
9529866ecbSAndrew Turner if (ix == 0xbf800000)
9629866ecbSAndrew Turner {
9729866ecbSAndrew Turner /* x == -1.0 => log1pf(x) = -Inf. */
9829866ecbSAndrew Turner return __math_divzerof (-1);
9929866ecbSAndrew Turner }
10029866ecbSAndrew Turner if (ia12 >= 0x7f8)
10129866ecbSAndrew Turner {
10229866ecbSAndrew Turner /* x == +/-NaN => log1pf(x) = NaN. */
10329866ecbSAndrew Turner return __math_invalidf (asfloat (ia));
10429866ecbSAndrew Turner }
10529866ecbSAndrew Turner /* x < -1.0 => log1pf(x) = NaN. */
10629866ecbSAndrew Turner return __math_invalidf (x);
10729866ecbSAndrew Turner }
10829866ecbSAndrew Turner
10929866ecbSAndrew Turner /* With x + 1 = t * 2^k (where t = m + 1 and k is chosen such that m
11029866ecbSAndrew Turner is in [-0.25, 0.5]):
11129866ecbSAndrew Turner log1p(x) = log(t) + log(2^k) = log1p(m) + k*log(2).
11229866ecbSAndrew Turner
11329866ecbSAndrew Turner We approximate log1p(m) with a polynomial, then scale by
11429866ecbSAndrew Turner k*log(2). Instead of doing this directly, we use an intermediate
11529866ecbSAndrew Turner scale factor s = 4*k*log(2) to ensure the scale is representable
11629866ecbSAndrew Turner as a normalised fp32 number. */
11729866ecbSAndrew Turner
11829866ecbSAndrew Turner if (ix <= 0x3f000000 || ia <= 0x3e800000)
11929866ecbSAndrew Turner {
12029866ecbSAndrew Turner /* If x is in [-0.25, 0.5] then we can shortcut all the logic
12129866ecbSAndrew Turner below, as k = 0 and m = x. All we need is to return the
12229866ecbSAndrew Turner polynomial. */
12329866ecbSAndrew Turner return eval_poly (x, e);
12429866ecbSAndrew Turner }
12529866ecbSAndrew Turner
12629866ecbSAndrew Turner float m = x + 1.0f;
12729866ecbSAndrew Turner
12829866ecbSAndrew Turner /* k is used scale the input. 0x3f400000 is chosen as we are trying to
12929866ecbSAndrew Turner reduce x to the range [-0.25, 0.5]. Inside this range, k is 0.
13029866ecbSAndrew Turner Outside this range, if k is reinterpreted as (NOT CONVERTED TO) float:
13129866ecbSAndrew Turner let k = sign * 2^p where sign = -1 if x < 0
13229866ecbSAndrew Turner 1 otherwise
13329866ecbSAndrew Turner and p is a negative integer whose magnitude increases with the
13429866ecbSAndrew Turner magnitude of x. */
13529866ecbSAndrew Turner int k = (asuint (m) - 0x3f400000) & 0xff800000;
13629866ecbSAndrew Turner
13729866ecbSAndrew Turner /* By using integer arithmetic, we obtain the necessary scaling by
13829866ecbSAndrew Turner subtracting the unbiased exponent of k from the exponent of x. */
13929866ecbSAndrew Turner float m_scale = asfloat (asuint (x) - k);
14029866ecbSAndrew Turner
14129866ecbSAndrew Turner /* Scale up to ensure that the scale factor is representable as normalised
14229866ecbSAndrew Turner fp32 number (s in [2**-126,2**26]), and scale m down accordingly. */
14329866ecbSAndrew Turner float s = asfloat (asuint (4.0f) - k);
14429866ecbSAndrew Turner m_scale = m_scale + fmaf (0.25f, s, -1.0f);
14529866ecbSAndrew Turner
14629866ecbSAndrew Turner float p = eval_poly (m_scale, biased_exponent (asuint (m_scale)));
14729866ecbSAndrew Turner
14829866ecbSAndrew Turner /* The scale factor to be applied back at the end - by multiplying float(k)
14929866ecbSAndrew Turner by 2^-23 we get the unbiased exponent of k. */
15029866ecbSAndrew Turner float scale_back = (float) k * 0x1.0p-23f;
15129866ecbSAndrew Turner
15229866ecbSAndrew Turner /* Apply the scaling back. */
15329866ecbSAndrew Turner return fmaf (scale_back, Ln2, p);
15429866ecbSAndrew Turner }
15529866ecbSAndrew Turner
1569d1de259SAndrew Turner TEST_SIG (S, F, 1, log1p, -0.9, 10.0)
1579d1de259SAndrew Turner TEST_ULP (log1pf, 1.52)
1589d1de259SAndrew Turner TEST_SYM_INTERVAL (log1pf, 0.0, 0x1p-23, 50000)
1599d1de259SAndrew Turner TEST_SYM_INTERVAL (log1pf, 0x1p-23, 0.001, 50000)
1609d1de259SAndrew Turner TEST_SYM_INTERVAL (log1pf, 0.001, 1.0, 50000)
1619d1de259SAndrew Turner TEST_SYM_INTERVAL (log1pf, 1.0, inf, 5000)
162