blob: e48b459f880b1c9b46c7e20b8f5fe50815c6c1b8 [file]
//===----------------------------------------------------------------------===//
//
// 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 "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 = typename 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
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
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
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 LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC___SUPPORT_MATH_EXPF_INTEGER_EVAL_H