1*f3087befSAndrew Turner /* 2*f3087befSAndrew Turner * Double-precision atanh(x) function. 3*f3087befSAndrew Turner * 4*f3087befSAndrew Turner * Copyright (c) 2022-2024, Arm Limited. 5*f3087befSAndrew Turner * SPDX-License-Identifier: MIT OR Apache-2.0 WITH LLVM-exception 6*f3087befSAndrew Turner */ 7*f3087befSAndrew Turner 8*f3087befSAndrew Turner #include "math_config.h" 9*f3087befSAndrew Turner #include "poly_scalar_f64.h" 10*f3087befSAndrew Turner #include "test_sig.h" 11*f3087befSAndrew Turner #include "test_defs.h" 12*f3087befSAndrew Turner 13*f3087befSAndrew Turner #define AbsMask 0x7fffffffffffffff 14*f3087befSAndrew Turner #define Half 0x3fe0000000000000 15*f3087befSAndrew Turner #define One 0x3ff0000000000000 16*f3087befSAndrew Turner #define Ln2Hi 0x1.62e42fefa3800p-1 17*f3087befSAndrew Turner #define Ln2Lo 0x1.ef35793c76730p-45 18*f3087befSAndrew Turner #define OneMHfRt2Top \ 19*f3087befSAndrew Turner 0x00095f62 /* top32(asuint64(1)) - top32(asuint64(sqrt(2)/2)). */ 20*f3087befSAndrew Turner #define OneTop12 0x3ff 21*f3087befSAndrew Turner #define HfRt2Top 0x3fe6a09e /* top32(asuint64(sqrt(2)/2)). */ 22*f3087befSAndrew Turner #define BottomMask 0xffffffff 23*f3087befSAndrew Turner 24*f3087befSAndrew Turner static inline double 25*f3087befSAndrew Turner log1p_inline (double x) 26*f3087befSAndrew Turner { 27*f3087befSAndrew Turner /* Helper for calculating log(1 + x) using order-18 polynomial on a reduced 28*f3087befSAndrew Turner interval. Copied from log1p_2u.c, with no special-case handling. See that 29*f3087befSAndrew Turner file for details of the algorithm. */ 30*f3087befSAndrew Turner double m = x + 1; 31*f3087befSAndrew Turner uint64_t mi = asuint64 (m); 32*f3087befSAndrew Turner 33*f3087befSAndrew Turner /* Decompose x + 1 into (f + 1) * 2^k, with k chosen such that f is in 34*f3087befSAndrew Turner [sqrt(2)/2, sqrt(2)]. */ 35*f3087befSAndrew Turner uint32_t u = (mi >> 32) + OneMHfRt2Top; 36*f3087befSAndrew Turner int32_t k = (int32_t) (u >> 20) - OneTop12; 37*f3087befSAndrew Turner uint32_t utop = (u & 0x000fffff) + HfRt2Top; 38*f3087befSAndrew Turner uint64_t u_red = ((uint64_t) utop << 32) | (mi & BottomMask); 39*f3087befSAndrew Turner double f = asdouble (u_red) - 1; 40*f3087befSAndrew Turner 41*f3087befSAndrew Turner /* Correction term for round-off in f. */ 42*f3087befSAndrew Turner double cm = (x - (m - 1)) / m; 43*f3087befSAndrew Turner 44*f3087befSAndrew Turner /* Approximate log1p(f) with polynomial. */ 45*f3087befSAndrew Turner double f2 = f * f; 46*f3087befSAndrew Turner double f4 = f2 * f2; 47*f3087befSAndrew Turner double f8 = f4 * f4; 48*f3087befSAndrew Turner double p = fma ( 49*f3087befSAndrew Turner f, estrin_18_f64 (f, f2, f4, f8, f8 * f8, __log1p_data.coeffs) * f, f); 50*f3087befSAndrew Turner 51*f3087befSAndrew Turner /* Recombine log1p(x) = k*log2 + log1p(f) + c/m. */ 52*f3087befSAndrew Turner double kd = k; 53*f3087befSAndrew Turner double y = fma (Ln2Lo, kd, cm); 54*f3087befSAndrew Turner return y + fma (Ln2Hi, kd, p); 55*f3087befSAndrew Turner } 56*f3087befSAndrew Turner 57*f3087befSAndrew Turner /* Approximation for double-precision inverse tanh(x), using a simplified 58*f3087befSAndrew Turner version of log1p. Greatest observed error is 3.00 ULP: 59*f3087befSAndrew Turner atanh(0x1.e58f3c108d714p-4) got 0x1.e7da77672a647p-4 60*f3087befSAndrew Turner want 0x1.e7da77672a64ap-4. */ 61*f3087befSAndrew Turner double 62*f3087befSAndrew Turner atanh (double x) 63*f3087befSAndrew Turner { 64*f3087befSAndrew Turner uint64_t ix = asuint64 (x); 65*f3087befSAndrew Turner uint64_t sign = ix & ~AbsMask; 66*f3087befSAndrew Turner uint64_t ia = ix & AbsMask; 67*f3087befSAndrew Turner 68*f3087befSAndrew Turner if (unlikely (ia == One)) 69*f3087befSAndrew Turner return __math_divzero (sign >> 32); 70*f3087befSAndrew Turner 71*f3087befSAndrew Turner if (unlikely (ia > One)) 72*f3087befSAndrew Turner return __math_invalid (x); 73*f3087befSAndrew Turner 74*f3087befSAndrew Turner double halfsign = asdouble (Half | sign); 75*f3087befSAndrew Turner double ax = asdouble (ia); 76*f3087befSAndrew Turner return halfsign * log1p_inline ((2 * ax) / (1 - ax)); 77*f3087befSAndrew Turner } 78*f3087befSAndrew Turner 79*f3087befSAndrew Turner TEST_SIG (S, D, 1, atanh, -1.0, 1.0) 80*f3087befSAndrew Turner TEST_ULP (atanh, 3.00) 81*f3087befSAndrew Turner TEST_SYM_INTERVAL (atanh, 0, 0x1p-23, 10000) 82*f3087befSAndrew Turner TEST_SYM_INTERVAL (atanh, 0x1p-23, 1, 90000) 83*f3087befSAndrew Turner TEST_SYM_INTERVAL (atanh, 1, inf, 100) 84