| //===----------------------------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| /// | |
| /// \file | |
| /// This file contains the statically rounded, integer-only implementation of | |
| /// expf(x) | |
| /// | |
| //===----------------------------------------------------------------------===// | |
| #ifndef LLVM_LIBC_SRC___SUPPORT_MATH_EXPF_INTEGER_EVAL_H | |
| #define LLVM_LIBC_SRC___SUPPORT_MATH_EXPF_INTEGER_EVAL_H | |
| #include "hdr/fenv_macros.h" | |
| #include "src/__support/CPP/bit.h" | |
| #include "src/__support/FPUtil/FPBits.h" | |
| #include "src/__support/FPUtil/PolyEval.h" | |
| #include "src/__support/frac64.h" | |
| #include "src/__support/macros/config.h" | |
| #include "src/__support/macros/optimization.h" | |
| #include "src/__support/math/check/exp_exceptions.h" | |
| namespace LIBC_NAMESPACE_DECL { | |
| namespace shared { | |
| namespace math { | |
| namespace static_rounding { | |
| // print(2+round(1/log(2), 64, RN)); | |
| // LSB(INV_LN2) = 2^-63 | |
| LIBC_INLINE_VAR constexpr Frac64 INV_LN2 = Frac64(0xb8aa'3b29'5c17'f0bc); | |
| // 64-bit polynomial approximation of 2^x coefficients generated with Sollya: | |
| // > P = fpminimax(2^x, 11, [|1, 64...|], [0, 1], absolute, fixed); | |
| // Store the fractional part of the coefficients below | |
| // > dirtyinfnorm(2^x - P(x), [0, 1]); | |
| // 0x1.6238...p-58 | |
| // LSB(EXPF_COEFFS[i]) = 2^-64 | |
| LIBC_INLINE_VAR constexpr Frac64 EXPF_COEFFS[] = { | |
| Frac64(0xb172'17f7'd1cf'b7cf), // x | |
| Frac64(0x3d7f'7bff'057d'4a5e), // x^2 | |
| Frac64(0x0e35'846b'8363'9484), // x^3 | |
| Frac64(0x0276'556d'ec97'dcd4), // x^4 | |
| Frac64(0x0057'61ff'dc04'c7ff), // x^5 | |
| Frac64(0x000a'1847'b6e7'92ec), // x^6 | |
| Frac64(0x0000'ffe8'14e5'7033), // x^7 | |
| Frac64(0x0000'1628'b6e9'70c8), // x^8 | |
| Frac64(0x0000'01b8'8ce7'4088), // x^9 | |
| Frac64(0x0000'001c'18d5'cb29), // x^10 | |
| Frac64(0x0000'0002'b43f'4490), // x^11 | |
| }; | |
| // Statically rounded, no except implementation of expf using integer-only | |
| // arithmetic. | |
| LIBC_INLINE float expf(float x, [[maybe_unused]] int rounding) { | |
| using FPBits = fputil::FPBits<float>; | |
| using FPBounds = LIBC_NAMESPACE::math::check::exp_internal::Bounds<float>; | |
| FPBits xbits(x); | |
| bool is_neg = xbits.is_neg(); | |
| uint32_t x_val = xbits.uintval(); | |
| uint32_t x_val_abs = x_val & 0x7fff'ffffU; | |
| // When |x| >= smallest value that will cause overflow, |x| <= 2^-25, or x is | |
| // NaN | |
| if (LIBC_UNLIKELY(x_val_abs >= FPBounds::UPPER_BITS || | |
| x_val_abs <= 0x3300'0000U)) { | |
| // |x| <= 2^-25 | |
| if (x_val_abs <= 0x3300'0000U) { | |
| #ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY | |
| return 1.0f; | |
| #else // !LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY | |
| if (x_val_abs == 0) | |
| return 1.0f; | |
| if (rounding == FE_UPWARD && !is_neg) | |
| return 0x1.000002p0f; | |
| if ((rounding == FE_DOWNWARD || rounding == FE_TOWARDZERO) && is_neg) | |
| return 0x1.fffffep-1f; | |
| return 1.0f; | |
| #endif // LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY | |
| } | |
| if (xbits.is_nan()) { | |
| // Per conversation with lntue, we don't need to raise exception here, | |
| // as we're assuming no FPUs/fenv in this kind of environment | |
| if (xbits.is_signaling_nan()) { | |
| // silencing | |
| return FPBits::quiet_nan().get_val(); | |
| } | |
| // quiet NaN | |
| return x; | |
| } | |
| // e^-inf = 0 | |
| // e^+inf = +inf | |
| if (xbits.is_inf()) { | |
| return is_neg ? 0.0f : FPBits::inf().get_val(); | |
| } | |
| // Large finite positive | |
| if (!is_neg) { | |
| #ifndef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY | |
| if (rounding == FE_DOWNWARD || rounding == FE_TOWARDZERO) | |
| return FPBits::max_normal().get_val(); | |
| return FPBits::inf().get_val(); | |
| #else // LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY | |
| return FPBits::inf().get_val(); | |
| #endif // !LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY | |
| } | |
| // x < log(2^-150) or NaN (NaN is already handled above) | |
| if (xbits.uintval() >= 0xc2cf'f1b5U) { | |
| #ifndef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY | |
| if (rounding == FE_UPWARD) | |
| return FPBits::min_subnormal().get_val(); | |
| #endif // !LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY | |
| return 0.0f; | |
| } | |
| } | |
| // Main calculations | |
| uint16_t x_e = xbits.get_biased_exponent(); | |
| uint64_t x_u = xbits.get_mantissa(); | |
| // Range reduction | |
| // The algorithm near the end of this function estimates 2^r, | |
| // where r is the fractional part of x * log2(e) and is in [0, 1]. | |
| // See EXPF_COEFFS for more details on the approximation polynomial used. | |
| // add leading bit = 1 | |
| x_u |= uint64_t(1) << FPBits::FRACTION_LEN; | |
| // shift to top 32 bit --> decimal point at hidden bit that we've added | |
| x_u <<= 32; | |
| int x_e_unbiased = static_cast<int>(x_e) - FPBits::EXP_BIAS; | |
| // shift for the decimal point to be at the hidden bit | |
| if (x_e_unbiased > 0) { | |
| x_u <<= x_e_unbiased; | |
| } else if (x_e_unbiased < 0) { | |
| x_u >>= -x_e_unbiased; | |
| } | |
| // LSB(x_u_frac) = 2^-55 | |
| Frac64 x_u_frac(x_u); | |
| // LSB(x_ln2) = 2^-54 | |
| Frac64 x_ln2 = x_u_frac * INV_LN2; | |
| constexpr uint64_t FRAC_MASK = (uint64_t(1) << 54) - 1; | |
| uint64_t x_ln2_bit = x_ln2.val[0]; | |
| uint64_t e_y, l2y_r; | |
| uint32_t e_y_unbiased; | |
| // As Frac64 can't store the sign, we need to handle the sign separately: | |
| // - Both branches are computing floor(x * log2(e)). | |
| // - For negative x, we round up to the next multiple of 2^54, then clear the | |
| // last 54 bits. | |
| // - For positive x, we round down (just clear) the last 54 bits. | |
| // | |
| // Then, l2y_r is the remainder of x * log2(e) after removing the integer | |
| // part, which is used to compute 2^l2y_r_frac - 1. | |
| // | |
| // e_y_unbiased is biased exponent field, but already bit-positioned to the | |
| // exponent field of the float representation. | |
| if (LIBC_UNLIKELY(is_neg)) { | |
| e_y = (x_ln2_bit + FRAC_MASK) & ~FRAC_MASK; | |
| l2y_r = e_y - x_ln2_bit; | |
| e_y_unbiased = (FPBits::EXP_BIAS << 23) - static_cast<uint32_t>(e_y >> 31); | |
| } else { | |
| e_y = x_ln2_bit & ~FRAC_MASK; | |
| l2y_r = x_ln2_bit - e_y; | |
| e_y_unbiased = (FPBits::EXP_BIAS << 23) + static_cast<uint32_t>(e_y >> 31); | |
| } | |
| uint32_t k = static_cast<uint32_t>(e_y >> 54); | |
| int d = static_cast<int>(k) - FPBits::EXP_BIAS; | |
| // d >= 24 --> k >= 151 | |
| // --> guaranteed to below 2^-150 | |
| // | |
| // underflow | |
| if (LIBC_UNLIKELY(is_neg && d >= 24)) { | |
| return 0.0f; | |
| } | |
| // LSB(l2y_r_frac) = LSB(l2y_r) * 2^-10 = 2^-64 | |
| Frac64 l2y_r_frac(l2y_r << 10); | |
| // p = 2^l2y_r_frac - 1 | |
| Frac64 p = l2y_r_frac * | |
| fputil::polyeval(l2y_r_frac, EXPF_COEFFS[0], EXPF_COEFFS[1], | |
| EXPF_COEFFS[2], EXPF_COEFFS[3], EXPF_COEFFS[4], | |
| EXPF_COEFFS[5], EXPF_COEFFS[6], EXPF_COEFFS[7], | |
| EXPF_COEFFS[8], EXPF_COEFFS[9], EXPF_COEFFS[10]); | |
| uint32_t shift_length = 40; | |
| uint32_t leading_one = 0; | |
| // We're computing with errors < worst-cast errors, so tie-rounding never | |
| // happens. Hence, round-to-nearest, tie-to-even is equivalent to | |
| // round-to-nearest, tie-to-away. Which is what we're implementing below | |
| // in the following order: | |
| // | |
| // 1. Shift so that the rounding bit is at bit-0 | |
| // 2. Add 1 for rounding | |
| // 3. Perform another shift by 1 | |
| // 4. Depending on the rounding modes, adjust accoringly: | |
| // a. Add 1 if rounding-up (0 if not) | |
| // b. Add e_y_unbiased to the result (0 if the result is subnormal) | |
| // subnormal | |
| if (LIBC_UNLIKELY(is_neg && d >= 0)) { | |
| e_y_unbiased = 0; | |
| leading_one = 1 << (23 - d); | |
| // In the below shifts, we're shifting by (shift_length + 1) at max, while | |
| // shift_length is already 40, and if d = 23 --> shift_length + d = 63, and | |
| // we'll shift by whole 64 bits, which is undefined behavior in C++. | |
| // | |
| // So, we'll truncate the last 2 bits. | |
| if (d >= 22) { | |
| d -= 2; | |
| p.val[0] >>= 2; | |
| } | |
| shift_length += d + 1; | |
| } | |
| #ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY | |
| uint32_t result = | |
| (static_cast<uint32_t>(p.val[0] >> shift_length) + (leading_one + 1)); | |
| result >>= 1; | |
| result += e_y_unbiased; | |
| return cpp::bit_cast<float>(result); | |
| #else // !LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY | |
| if (rounding == FE_TONEAREST) { | |
| uint32_t result = | |
| (static_cast<uint32_t>(p.val[0] >> shift_length) + (leading_one + 1)); | |
| result >>= 1; | |
| result += e_y_unbiased; | |
| return cpp::bit_cast<float>(result); | |
| } | |
| uint32_t should_round_up = 0; | |
| if (LIBC_UNLIKELY(rounding == FE_UPWARD)) { | |
| uint64_t round_up_mask = (uint64_t(1) << (shift_length + 1)) - 1; | |
| should_round_up = static_cast<uint32_t>((p.val[0] & round_up_mask) != 0); | |
| } | |
| uint32_t result = (static_cast<uint32_t>(p.val[0] >> (shift_length + 1)) + | |
| should_round_up + (leading_one >> 1)); | |
| result += e_y_unbiased; | |
| return cpp::bit_cast<float>(result); | |
| #endif // LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY | |
| } | |
| } // namespace static_rounding | |
| } // namespace math | |
| } // namespace shared | |
| namespace math { | |
| namespace integer_eval { | |
| LIBC_INLINE float expf(float x) { | |
| return shared::math::static_rounding::expf(x, FE_TONEAREST); | |
| } | |
| } // namespace integer_eval | |
| } // namespace math | |
| } // namespace LIBC_NAMESPACE_DECL | |
| #endif // LLVM_LIBC_SRC___SUPPORT_MATH_EXPF_INTEGER_EVAL_H |