[libc-commits] [libc] a454a3c - [libc][math] Integer-only, statically rounded implementation of expf (#209406)
via libc-commits
libc-commits at lists.llvm.org
Sun Aug 23 08:34:46 PDT 2026
Author: Hoàng Minh Thiên
Date: 2026-08-23T11:34:41-04:00
New Revision: a454a3cdaf82dea7b6293d29628b70eaec918723
URL: https://github.com/llvm/llvm-project/commit/a454a3cdaf82dea7b6293d29628b70eaec918723
DIFF: https://github.com/llvm/llvm-project/commit/a454a3cdaf82dea7b6293d29628b70eaec918723.diff
LOG: [libc][math] Integer-only, statically rounded implementation of expf (#209406)
Integer-only, statically rounded implementation of `expf`, using 1
single `Frac64`-based pipeline
# Accuracy
All unit, smoke, and exhaustive tests against
`LIBC_NAMESPACE::shared::expf` pass for all rounding modes.
# Code size:
> [!NOTE]
> The report below are from the`armv8m.main-none-eabi` triple.
## Before the patch
```sh
$ ls -lB libc/src/math/generic/CMakeFiles/libc.src.math.generic.expf.dir/
total 16
-rw-r-----@ 1 davidhoang staff 5520 Jul 24 15:59 expf.cpp.obj
$ llvm-nm --radix=d --print-size --size-sort --reverse-sort libc/src/math/generic/CMakeFiles/libc.src.math.generic.expf.dir/expf.cpp.obj
00000000 00001560 V _ZN22__llvm_libc_24_0_0_git4math6EXP_M1E
00000000 00001024 V _ZN22__llvm_libc_24_0_0_git4math6EXP_M2E
00000000 00000628 W _ZN22__llvm_libc_24_0_0_git4math4expfEf
00000000 00000012 T expf
00000000 00000012 T _ZN22__llvm_libc_24_0_0_git4expfEf
00000000 00000004 V _ZZN22__llvm_libc_24_0_0_git6fputil7generic16fenv_is_round_upEvE1x
00000000 00000004 V _ZZN22__llvm_libc_24_0_0_git6fputil7generic15quick_get_roundEvE1x
U __aeabi_f2d
U __aeabi_dmul
U __aeabi_dadd
U __aeabi_d2f
```
## After the patch and with `LIBC_MATH_FAST`
```sh
$ ls -lB libc/src/math/generic/CMakeFiles/libc.src.math.generic.expf.dir/
total 8
-rw-r-----@ 1 davidhoang staff 3244 Aug 18 14:29 expf.cpp.obj
$ llvm-nm --radix=d --print-size --size-sort --reverse-sort libc/src/math/generic/CMakeFiles/libc.src.math.generic.expf.dir/expf.cpp.obj
00000000 00001916 W _ZN22__llvm_libc_24_0_0_git6shared4math15static_rounding4expfEfi
00000000 00000014 T expf
00000000 00000014 T _ZN22__llvm_libc_24_0_0_git4expfEf
```
# Latency
[bench.zip](https://github.com/user-attachments/files/31171370/bench.zip),
containing `bench.cc` with compilation instruction.
```
====================LIBC_MATH_FAST is ON====================
==========================baseline==========================
overflow [88,100] 51.67 ns/call (19M ops/sec)
underflow [-110,-100] 55.50 ns/call (18M ops/sec)
normal [-10,10] 32.09 ns/call (31M ops/sec)
subnormal-result [-103,-87] 40.14 ns/call (25M ops/sec)
near-underflow [-100,-80] 40.84 ns/call (24M ops/sec)
======================static_rounding=======================
overflow [88,100] 142.13 ns/call (7M ops/sec)
underflow [-110,-100] 431.57 ns/call (2M ops/sec)
normal [-10,10] 925.12 ns/call (1M ops/sec)
subnormal-result [-103,-87] 965.58 ns/call (1M ops/sec)
near-underflow [-100,-80] 955.55 ns/call (1M ops/sec)
```
```
===================LIBC_MATH_FAST is OFF====================
==========================baseline==========================
overflow [88,100] 55.99 ns/call (18M ops/sec)
underflow [-110,-100] 58.80 ns/call (17M ops/sec)
normal [-10,10] 32.70 ns/call (31M ops/sec)
subnormal-result [-103,-87] 40.90 ns/call (24M ops/sec)
near-underflow [-100,-80] 40.46 ns/call (25M ops/sec)
======================static_rounding=======================
overflow [88,100] 141.72 ns/call (7M ops/sec)
underflow [-110,-100] 432.78 ns/call (2M ops/sec)
normal [-10,10] 935.92 ns/call (1M ops/sec)
subnormal-result [-103,-87] 971.07 ns/call (1M ops/sec)
near-underflow [-100,-80] 966.82 ns/call (1M ops/sec)
```
Benchmarking system: MacBook Air M3, macOS 26.5.1
Added:
libc/shared/math/static_rounding/expf.h
libc/shared/static_rounding_math.h
libc/src/__support/frac64.h
libc/src/__support/math/expf_integer_eval.h
libc/test/src/math/exhaustive/exhaustive_test_static_rounding.h
libc/test/src/math/exhaustive/expf_static_rounding_test.cpp
libc/test/src/math/expf_static_rounding_test.cpp
libc/test/src/math/smoke/expf_static_rounding_test.cpp
Modified:
libc/src/__support/CMakeLists.txt
libc/src/__support/math/CMakeLists.txt
libc/src/math/generic/expf.cpp
libc/test/UnitTest/FPMatcher.h
libc/test/UnitTest/RoundingModeUtils.cpp
libc/test/UnitTest/RoundingModeUtils.h
libc/test/src/math/CMakeLists.txt
libc/test/src/math/exhaustive/CMakeLists.txt
libc/test/src/math/smoke/CMakeLists.txt
Removed:
################################################################################
diff --git a/libc/shared/math/static_rounding/expf.h b/libc/shared/math/static_rounding/expf.h
new file mode 100644
index 0000000000000..0f4aeef673ba7
--- /dev/null
+++ b/libc/shared/math/static_rounding/expf.h
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 shared statically-rounded expf(x) function
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SHARED_MATH_STATIC_ROUNDING_EXPF_H
+#define LLVM_LIBC_SHARED_MATH_STATIC_ROUNDING_EXPF_H
+
+#include "shared/libc_common.h"
+#include "src/__support/math/expf_integer_eval.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace shared {
+
+using math::static_rounding::expf;
+
+} // namespace shared
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SHARED_MATH_STATIC_ROUNDING_EXPF_H
diff --git a/libc/shared/static_rounding_math.h b/libc/shared/static_rounding_math.h
new file mode 100644
index 0000000000000..5488354a9b552
--- /dev/null
+++ b/libc/shared/static_rounding_math.h
@@ -0,0 +1,20 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 implementations of math functions
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SHARED_STATIC_ROUNDING_MATH_H
+#define LLVM_LIBC_SHARED_STATIC_ROUNDING_MATH_H
+
+#include "shared/libc_common.h"
+#include "shared/math/static_rounding/expf.h"
+
+#endif // LLVM_LIBC_SHARED_STATIC_ROUNDING_MATH_H
diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt
index ef07690e5ff9b..897a4c63e6017 100644
--- a/libc/src/__support/CMakeLists.txt
+++ b/libc/src/__support/CMakeLists.txt
@@ -395,6 +395,15 @@ add_header_library(
libc.src.__support.macros.config
)
+add_header_library(
+ frac64
+ HDRS
+ frac64.h
+ DEPENDS
+ .big_int
+ libc.src.__support.macros.config
+)
+
add_header_library(
uint128
HDRS
diff --git a/libc/src/__support/frac64.h b/libc/src/__support/frac64.h
new file mode 100644
index 0000000000000..bbb0d3bb30903
--- /dev/null
+++ b/libc/src/__support/frac64.h
@@ -0,0 +1,58 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 declaration of 64-bit unsigned fractional type
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_FRAC64_H
+#define LLVM_LIBC_SRC___SUPPORT_FRAC64_H
+
+#include "src/__support/big_int.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct Frac64 : public UInt<64> {
+ using UInt<64>::UInt;
+
+ LIBC_INLINE constexpr Frac64 operator~() const { return Frac64(~val[0]); }
+
+ LIBC_INLINE constexpr Frac64 operator+(Frac64 other) const {
+ return Frac64(val[0] + other.val[0]);
+ }
+
+ LIBC_INLINE constexpr Frac64 operator-(Frac64 other) const {
+ return Frac64(val[0] - other.val[0]);
+ }
+
+ LIBC_INLINE constexpr Frac64 operator*(Frac64 other) const {
+ UInt<64> r = UInt<64>::quick_mul_hi(UInt<64>(other));
+ return Frac64(r.val[0]);
+ }
+
+ LIBC_INLINE constexpr Frac64 &operator+=(Frac64 other) {
+ *this = *this + other;
+ return *this;
+ }
+
+ LIBC_INLINE constexpr Frac64 &operator-=(Frac64 other) {
+ *this = *this - other;
+ return *this;
+ }
+
+ LIBC_INLINE constexpr Frac64 &operator*=(Frac64 other) {
+ *this = *this * other;
+ return *this;
+ }
+};
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_FRAC64_H
diff --git a/libc/src/__support/math/CMakeLists.txt b/libc/src/__support/math/CMakeLists.txt
index e9325bb17cfdb..2b63cc6ee4cba 100644
--- a/libc/src/__support/math/CMakeLists.txt
+++ b/libc/src/__support/math/CMakeLists.txt
@@ -3085,6 +3085,19 @@ add_header_library(
libc.src.__support.macros.optimization
)
+add_header_library(
+ expf_integer_eval
+ HDRS
+ expf_integer_eval.h
+ DEPENDS
+ .exp_constants
+ libc.src.__support.CPP.bit
+ libc.src.__support.FPUtil.fp_bits
+ libc.src.__support.macros.config
+ libc.src.__support.macros.optimization
+ libc.src.__support.frac64
+)
+
add_header_library(
expf16_utils
HDRS
diff --git a/libc/src/__support/math/expf_integer_eval.h b/libc/src/__support/math/expf_integer_eval.h
new file mode 100644
index 0000000000000..e48b459f880b1
--- /dev/null
+++ b/libc/src/__support/math/expf_integer_eval.h
@@ -0,0 +1,284 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
diff --git a/libc/src/math/generic/expf.cpp b/libc/src/math/generic/expf.cpp
index de11f51ac64a0..1ec621b22677f 100644
--- a/libc/src/math/generic/expf.cpp
+++ b/libc/src/math/generic/expf.cpp
@@ -7,10 +7,19 @@
//===----------------------------------------------------------------------===//
#include "src/math/expf.h"
+#include "shared/math/static_rounding/expf.h"
#include "src/__support/math/expf.h"
namespace LIBC_NAMESPACE_DECL {
-LLVM_LIBC_FUNCTION(float, expf, (float x)) { return math::expf(x); }
+LLVM_LIBC_FUNCTION(float, expf, (float x)) {
+#if !defined(LIBC_TARGET_CPU_HAS_FPU_DOUBLE) && \
+ defined(LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY) && \
+ defined(LIBC_MATH_HAS_NO_EXCEPT) && defined(LIBC_MATH_HAS_NO_ERRNO)
+ return shared::math::static_rounding::expf(x, FE_TONEAREST);
+#else
+ return math::expf(x);
+#endif
+}
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/UnitTest/FPMatcher.h b/libc/test/UnitTest/FPMatcher.h
index 41188d2ad09d1..314d8f4f1ef1e 100644
--- a/libc/test/UnitTest/FPMatcher.h
+++ b/libc/test/UnitTest/FPMatcher.h
@@ -184,8 +184,7 @@ template <typename T> struct FPTest : public ErrnoCheckingTest {
static constexpr T neg_zero = FPBits::zero(Sign::NEG).get_val();
static constexpr T aNaN = FPBits::quiet_nan(Sign::POS).get_val();
static constexpr T neg_aNaN = FPBits::quiet_nan(Sign::NEG).get_val();
- // TODO: make this static constexpr
- const T sNaN = FPBits::signaling_nan().get_val();
+ static constexpr T sNaN = FPBits::signaling_nan().get_val();
static constexpr T inf = FPBits::inf(Sign::POS).get_val();
static constexpr T neg_inf = FPBits::inf(Sign::NEG).get_val();
static constexpr T min_normal = FPBits::min_normal().get_val();
diff --git a/libc/test/UnitTest/RoundingModeUtils.cpp b/libc/test/UnitTest/RoundingModeUtils.cpp
index 324d9b8a5f897..1921c1138b70a 100644
--- a/libc/test/UnitTest/RoundingModeUtils.cpp
+++ b/libc/test/UnitTest/RoundingModeUtils.cpp
@@ -28,7 +28,8 @@ int get_fe_rounding(RoundingMode mode) {
case RoundingMode::Nearest:
return FE_TONEAREST;
}
- __builtin_unreachable();
+
+ return -1;
}
ForceRoundingMode::ForceRoundingMode(RoundingMode mode) {
@@ -39,9 +40,14 @@ ForceRoundingMode::ForceRoundingMode(RoundingMode mode) {
#else
old_rounding_mode = quick_get_round();
rounding_mode = get_fe_rounding(mode);
+
if (old_rounding_mode != rounding_mode) {
- int status = set_round(rounding_mode);
- success = (status == 0);
+ if (rounding_mode == -1) {
+ success = false;
+ } else {
+ int status = set_round(rounding_mode);
+ success = (status == 0);
+ }
} else {
success = true;
}
diff --git a/libc/test/UnitTest/RoundingModeUtils.h b/libc/test/UnitTest/RoundingModeUtils.h
index 5f23a8708fe68..353894302096a 100644
--- a/libc/test/UnitTest/RoundingModeUtils.h
+++ b/libc/test/UnitTest/RoundingModeUtils.h
@@ -31,6 +31,8 @@ template <RoundingMode R> struct ForceRoundingModeTest : ForceRoundingMode {
ForceRoundingModeTest() : ForceRoundingMode(R) {}
};
+int get_fe_rounding(RoundingMode mode);
+
} // namespace testing
} // namespace fputil
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/math/CMakeLists.txt b/libc/test/src/math/CMakeLists.txt
index 90c29922d5a35..9f325673f09f1 100644
--- a/libc/test/src/math/CMakeLists.txt
+++ b/libc/test/src/math/CMakeLists.txt
@@ -1241,6 +1241,18 @@ add_fp_unittest(
libc.src.__support.FPUtil.fp_bits
)
+add_fp_unittest(
+ expf_static_rounding_test
+ NEED_MPFR
+ SUITE
+ libc-math-unittests
+ SRCS
+ expf_static_rounding_test.cpp
+ DEPENDS
+ libc.src.errno.errno
+ libc.src.__support.FPUtil.fp_bits
+)
+
add_fp_unittest(
expf16_test
NEED_MPFR
diff --git a/libc/test/src/math/exhaustive/CMakeLists.txt b/libc/test/src/math/exhaustive/CMakeLists.txt
index 2d1301d3a1e66..b04ae10ae47c5 100644
--- a/libc/test/src/math/exhaustive/CMakeLists.txt
+++ b/libc/test/src/math/exhaustive/CMakeLists.txt
@@ -10,6 +10,16 @@ add_header_library(
libc.src.__support.macros.properties.types
)
+add_header_library(
+ exhaustive_test_static_rounding
+ HDRS
+ exhaustive_test_static_rounding.h
+ DEPENDS
+ libc.src.__support.CPP.type_traits
+ libc.src.__support.FPUtil.fp_bits
+ libc.src.__support.macros.properties.types
+)
+
add_fp_unittest(
sqrtf_test
NO_RUN_POSTBUILD
@@ -215,6 +225,22 @@ add_fp_unittest(
-lpthread
)
+add_fp_unittest(
+ expf_static_rounding_test
+ NO_RUN_POSTBUILD
+ SUITE
+ libc_math_exhaustive_tests
+ SRCS
+ expf_static_rounding_test.cpp
+ DEPENDS
+ .exhaustive_test_static_rounding
+ libc.src.__support.math.expf
+ libc.src.__support.math.expf_integer_eval
+ libc.src.__support.FPUtil.fp_bits
+ LINK_LIBRARIES
+ -lpthread
+)
+
add_fp_unittest(
exp2f_test
NO_RUN_POSTBUILD
diff --git a/libc/test/src/math/exhaustive/exhaustive_test_static_rounding.h b/libc/test/src/math/exhaustive/exhaustive_test_static_rounding.h
new file mode 100644
index 0000000000000..7183b43e615e8
--- /dev/null
+++ b/libc/test/src/math/exhaustive/exhaustive_test_static_rounding.h
@@ -0,0 +1,230 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 exhaustive test template for statically rounded math
+/// functions
+///
+//===----------------------------------------------------------------------===//
+
+// This file is modeled after exhaustive_test.h, modified for testing statically
+// rounded math functions.
+
+#include "exhaustive_test.h"
+#include "src/__support/CPP/type_traits.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/macros/properties/types.h"
+#include "test/UnitTest/FPMatcher.h"
+#include "test/UnitTest/RoundingModeUtils.h"
+#include "test/UnitTest/Test.h"
+#include "test/UnitTest/TestLogger.h"
+
+#include <atomic>
+#include <iostream>
+#include <mutex>
+#include <sstream>
+#include <thread>
+#include <vector>
+
+template <typename OutType, typename InType = OutType>
+using StaticallyRoundedUnaryOp = OutType(InType, int);
+
+template <typename OutType, typename InType,
+ UnaryOp<OutType, InType> BaselineFunc,
+ StaticallyRoundedUnaryOp<OutType, InType> Func>
+struct StaticallyRoundedUnaryOpChecker
+ : public virtual LIBC_NAMESPACE::testing::Test {
+ using FloatType = InType;
+ using FPBits = LIBC_NAMESPACE::fputil::FPBits<FloatType>;
+ using StorageType = typename FPBits::StorageType;
+ using RoundingMode = LIBC_NAMESPACE::fputil::testing::RoundingMode;
+ using ForceRoundingMode = LIBC_NAMESPACE::fputil::testing::ForceRoundingMode;
+
+ // Check in a range, return the number of failures.
+ uint64_t check(StorageType start, StorageType stop, RoundingMode rounding) {
+ ForceRoundingMode r(rounding);
+ if (!r.success)
+ return (stop > start);
+
+ // ForceRoundingMode already checks for valid FE_* rounding mode
+ using LIBC_NAMESPACE::fputil::testing::get_fe_rounding;
+ const int fenv_rounding = get_fe_rounding(rounding);
+
+ StorageType bits = start;
+ uint64_t failed = 0;
+
+ do {
+ FPBits xbits(bits);
+ FloatType x = xbits.get_val();
+ bool correct = TEST_FP_EQ(BaselineFunc(x), Func(x, fenv_rounding));
+ failed += (!correct);
+ // Uncomment to print out failed values.
+ if (!correct) {
+ EXPECT_FP_EQ_ROUNDING_MODE(BaselineFunc(x), Func(x, fenv_rounding),
+ rounding);
+ }
+ } while (bits++ < stop);
+
+ return failed;
+ }
+};
+
+// Modeled after `LlvmLibcExhaustiveMathTest`
+//
+// Checker class needs inherit from LIBC_NAMESPACE::testing::Test and provide
+// StorageType and check method.
+template <typename Checker, size_t Increment = 1 << 20>
+struct LlvmLibcExhaustiveStaticallyRoundedMathTest
+ : public virtual LIBC_NAMESPACE::testing::Test,
+ public Checker {
+ using FloatType = typename Checker::FloatType;
+ using FPBits = typename Checker::FPBits;
+ using StorageType = typename Checker::StorageType;
+ using RoundingMode = typename LIBC_NAMESPACE::fputil::testing::RoundingMode;
+
+ void explain_failed_range(std::stringstream &msg, StorageType x_begin,
+ StorageType x_end) {
+#ifdef LIBC_TYPES_HAS_FLOAT16
+ using T = LIBC_NAMESPACE::cpp::conditional_t<
+ LIBC_NAMESPACE::cpp::is_same_v<FloatType, float16>, float, FloatType>;
+#else
+ using T = FloatType;
+#endif
+
+ msg << x_begin << " to " << x_end << " [0x" << std::hex << x_begin << ", 0x"
+ << x_end << "), [" << std::hexfloat
+ << static_cast<T>(FPBits(x_begin).get_val()) << ", "
+ << static_cast<T>(FPBits(x_end).get_val()) << ")";
+ }
+
+ void explain_failed_range(std::stringstream &msg, StorageType x_begin,
+ StorageType x_end, StorageType y_begin,
+ StorageType y_end) {
+ msg << "x ";
+ explain_failed_range(msg, x_begin, x_end);
+ msg << ", y ";
+ explain_failed_range(msg, y_begin, y_end);
+ }
+
+ // Break [start, stop) into `nthreads` subintervals and apply *check to each
+ // subinterval in parallel.
+ template <typename... T>
+ void test_full_range(RoundingMode rounding, StorageType start,
+ StorageType stop, T... extra_range_bounds) {
+ int n_threads = std::thread::hardware_concurrency();
+ std::vector<std::thread> thread_list;
+ std::mutex mx_cur_val;
+ int current_percent = -1;
+ StorageType current_value = start;
+ std::atomic<uint64_t> failed(0);
+
+ for (int i = 0; i < n_threads; ++i) {
+ thread_list.emplace_back([&, this]() {
+ while (true) {
+ StorageType range_begin, range_end;
+ int new_percent = -1;
+ {
+ std::lock_guard<std::mutex> lock(mx_cur_val);
+ if (current_value == stop)
+ return;
+
+ range_begin = current_value;
+ if (stop >= Increment && stop - Increment >= current_value) {
+ range_end = static_cast<StorageType>(current_value + Increment);
+ } else {
+ range_end = stop;
+ }
+ current_value = range_end;
+ int pc =
+ static_cast<int>(100.0 * (range_end - start) / (stop - start));
+ if (current_percent != pc) {
+ new_percent = pc;
+ current_percent = pc;
+ }
+ }
+ if (new_percent >= 0) {
+ std::stringstream msg;
+ msg << new_percent << "% is in process \r";
+ std::cout << msg.str() << std::flush;
+ }
+
+ uint64_t failed_in_range = Checker::check(
+ range_begin, range_end, extra_range_bounds..., rounding);
+ if (failed_in_range > 0) {
+ std::stringstream msg;
+ msg << "Test failed for " << std::dec << failed_in_range
+ << " inputs in range: ";
+ explain_failed_range(msg, range_begin, range_end,
+ extra_range_bounds...);
+ msg << "\n";
+ std::cerr << msg.str() << std::flush;
+
+ failed.fetch_add(failed_in_range);
+ }
+ }
+ });
+ }
+
+ for (auto &thread : thread_list) {
+ if (thread.joinable()) {
+ thread.join();
+ }
+ }
+
+ std::cout << std::endl;
+ std::cout << "Test " << ((failed > 0) ? "FAILED" : "PASSED") << std::endl;
+ ASSERT_EQ(failed.load(), uint64_t(0));
+ }
+
+ void test_full_range_all_roundings(StorageType start, StorageType stop) {
+ std::cout << "-- Testing for FE_TONEAREST in range [0x" << std::hex << start
+ << ", 0x" << stop << ") --" << std::dec << std::endl;
+ test_full_range(RoundingMode::Nearest, start, stop);
+
+ std::cout << "-- Testing for FE_UPWARD in range [0x" << std::hex << start
+ << ", 0x" << stop << ") --" << std::dec << std::endl;
+ test_full_range(RoundingMode::Upward, start, stop);
+
+ std::cout << "-- Testing for FE_DOWNWARD in range [0x" << std::hex << start
+ << ", 0x" << stop << ") --" << std::dec << std::endl;
+ test_full_range(RoundingMode::Downward, start, stop);
+
+ std::cout << "-- Testing for FE_TOWARDZERO in range [0x" << std::hex
+ << start << ", 0x" << stop << ") --" << std::dec << std::endl;
+ test_full_range(RoundingMode::TowardZero, start, stop);
+ }
+
+ void test_full_range_all_roundings(StorageType x_start, StorageType x_stop,
+ StorageType y_start, StorageType y_stop) {
+ std::cout << "-- Testing for FE_TONEAREST in x range [0x" << std::hex
+ << x_start << ", 0x" << x_stop << "), y range [0x" << y_start
+ << ", 0x" << y_stop << ") --" << std::dec << std::endl;
+ test_full_range(RoundingMode::Nearest, x_start, x_stop, y_start, y_stop);
+
+ std::cout << "-- Testing for FE_UPWARD in x range [0x" << std::hex
+ << x_start << ", 0x" << x_stop << "), y range [0x" << y_start
+ << ", 0x" << y_stop << ") --" << std::dec << std::endl;
+ test_full_range(RoundingMode::Upward, x_start, x_stop, y_start, y_stop);
+
+ std::cout << "-- Testing for FE_DOWNWARD in x range [0x" << std::hex
+ << x_start << ", 0x" << x_stop << "), y range [0x" << y_start
+ << ", 0x" << y_stop << ") --" << std::dec << std::endl;
+ test_full_range(RoundingMode::Downward, x_start, x_stop, y_start, y_stop);
+
+ std::cout << "-- Testing for FE_TOWARDZERO in x range [0x" << std::hex
+ << x_start << ", 0x" << x_stop << "), y range [0x" << y_start
+ << ", 0x" << y_stop << ") --" << std::dec << std::endl;
+ test_full_range(RoundingMode::TowardZero, x_start, x_stop, y_start, y_stop);
+ }
+};
+
+template <typename FloatType, UnaryOp<FloatType> BaselineFunc,
+ StaticallyRoundedUnaryOp<FloatType> Func>
+using LlvmLibcStaticallyRoundedUnaryOpExhaustiveMathTest =
+ LlvmLibcExhaustiveStaticallyRoundedMathTest<StaticallyRoundedUnaryOpChecker<
+ FloatType, FloatType, BaselineFunc, Func>>;
diff --git a/libc/test/src/math/exhaustive/expf_static_rounding_test.cpp b/libc/test/src/math/exhaustive/expf_static_rounding_test.cpp
new file mode 100644
index 0000000000000..c75234547973f
--- /dev/null
+++ b/libc/test/src/math/exhaustive/expf_static_rounding_test.cpp
@@ -0,0 +1,39 @@
+
+//===----------------------------------------------------------------------===//
+//
+// 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 exhaustive tests for statically-rounded
+/// implementation of static_rounding::expf(x)
+///
+//===----------------------------------------------------------------------===//
+
+#include "exhaustive_test_static_rounding.h"
+#include "src/__support/math/expf.h"
+#include "src/__support/math/expf_integer_eval.h"
+
+using LlvmLibcStaticallyRoundedExpfExhaustiveTest =
+ LlvmLibcStaticallyRoundedUnaryOpExhaustiveMathTest<
+ float, LIBC_NAMESPACE::math::expf,
+ LIBC_NAMESPACE::shared::math::static_rounding::expf>;
+
+// Range: [0, Inf];
+static constexpr uint32_t POS_START = 0x0000'0000U;
+static constexpr uint32_t POS_STOP = 0x7f80'0000U;
+
+TEST_F(LlvmLibcStaticallyRoundedExpfExhaustiveTest, PostiveRange) {
+ test_full_range_all_roundings(POS_START, POS_STOP);
+}
+
+// Range: [-Inf, 0];
+static constexpr uint32_t NEG_START = 0xb000'0000U;
+static constexpr uint32_t NEG_STOP = 0xff80'0000U;
+
+TEST_F(LlvmLibcStaticallyRoundedExpfExhaustiveTest, NegativeRange) {
+ test_full_range_all_roundings(NEG_START, NEG_STOP);
+}
diff --git a/libc/test/src/math/expf_static_rounding_test.cpp b/libc/test/src/math/expf_static_rounding_test.cpp
new file mode 100644
index 0000000000000..9befb1de10972
--- /dev/null
+++ b/libc/test/src/math/expf_static_rounding_test.cpp
@@ -0,0 +1,133 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 unit tests for statically-rounded implementation of
+/// static_rounding::expf(x)
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/math_macros.h"
+#include "hdr/stdint_proxy.h"
+#include "shared/static_rounding_math.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/optimization.h"
+#include "src/__support/math/expf.h"
+#include "test/UnitTest/FPMatcher.h"
+#include "test/UnitTest/RoundingModeUtils.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcExpfStaticRoundingTest = LIBC_NAMESPACE::testing::FPTest<float>;
+using RoundingMode = LIBC_NAMESPACE::fputil::testing::RoundingMode;
+
+namespace static_rounding = LIBC_NAMESPACE::shared::math::static_rounding;
+namespace math = LIBC_NAMESPACE::math;
+
+TEST_F(LlvmLibcExpfStaticRoundingTest, SpecialNumbers) {
+ using LIBC_NAMESPACE::fputil::testing::get_fe_rounding;
+
+ for (auto rounding : ROUNDING_MODES) {
+ const int fenv_rounding = get_fe_rounding(rounding);
+ EXPECT_FP_EQ_ROUNDING_MODE(
+ math::expf(aNaN), static_rounding::expf(aNaN, fenv_rounding), rounding);
+ EXPECT_MATH_ERRNO(0);
+
+ EXPECT_FP_EQ_ROUNDING_MODE(
+ math::expf(inf), static_rounding::expf(inf, fenv_rounding), rounding);
+ EXPECT_MATH_ERRNO(0);
+
+ EXPECT_FP_EQ_ROUNDING_MODE(math::expf(neg_inf),
+ static_rounding::expf(neg_inf, fenv_rounding),
+ rounding);
+ EXPECT_MATH_ERRNO(0);
+
+ EXPECT_FP_EQ_ROUNDING_MODE(
+ math::expf(0.0f), static_rounding::expf(0.0f, fenv_rounding), rounding);
+ EXPECT_MATH_ERRNO(0);
+
+ EXPECT_FP_EQ_ROUNDING_MODE(math::expf(-0.0f),
+ static_rounding::expf(-0.0f, fenv_rounding),
+ rounding);
+ EXPECT_MATH_ERRNO(0);
+ }
+}
+
+TEST_F(LlvmLibcExpfStaticRoundingTest, Overflow) {
+ constexpr float VALUES[] = {FPBits(0x7f7fffffU).get_val(),
+ FPBits(0x42cffff8U).get_val(),
+ FPBits(0x42d00008U).get_val()};
+
+ for (auto rounding : ROUNDING_MODES) {
+ const int fenv_rounding = get_fe_rounding(rounding);
+
+ // Statically rounded expf doesn't raise exceptions
+
+ for (auto x : VALUES) {
+ EXPECT_FP_EQ_ROUNDING_MODE(
+ math::expf(x), static_rounding::expf(x, fenv_rounding), rounding);
+ }
+ }
+}
+
+TEST_F(LlvmLibcExpfStaticRoundingTest, Underflow) {
+ using LIBC_NAMESPACE::fputil::testing::get_fe_rounding;
+
+ constexpr float VALUES[] = {FPBits(0xff7fffffU).get_val(),
+ FPBits(0xc2cffff8U).get_val(),
+ FPBits(0xc2d00008U).get_val()};
+
+ for (auto rounding : ROUNDING_MODES) {
+ const int fenv_rounding = get_fe_rounding(rounding);
+
+ // Statically rounded expf doesn't raise exceptions
+ for (auto x : VALUES) {
+ EXPECT_FP_EQ_ROUNDING_MODE(
+ math::expf(x), static_rounding::expf(x, fenv_rounding), rounding);
+ }
+ }
+}
+
+// Test with inputs which are the borders of underflow/overflow but still
+// produce valid results without setting errno.
+TEST_F(LlvmLibcExpfStaticRoundingTest, Borderline) {
+ using LIBC_NAMESPACE::fputil::testing::get_fe_rounding;
+
+ constexpr float VALUES[] = {
+ FPBits(0x42affff8U).get_val(), FPBits(0x42b00008U).get_val(),
+ FPBits(0xc2affff8U).get_val(), FPBits(0xc2b00008U).get_val(),
+ FPBits(0xc236bd8cU).get_val()};
+
+ for (auto rounding : ROUNDING_MODES) {
+ const int fenv_rounding = get_fe_rounding(rounding);
+
+ for (auto x : VALUES) {
+ EXPECT_FP_EQ_ROUNDING_MODE(
+ math::expf(x), static_rounding::expf(x, fenv_rounding), rounding);
+ }
+ }
+}
+
+TEST_F(LlvmLibcExpfStaticRoundingTest, InFloatRange) {
+ using LIBC_NAMESPACE::fputil::testing::get_fe_rounding;
+
+ constexpr uint32_t COUNT = 1'231;
+ constexpr uint32_t STEP = UINT32_MAX / COUNT;
+ for (auto rounding : ROUNDING_MODES) {
+ const int fenv_rounding = get_fe_rounding(rounding);
+
+ for (uint32_t i = 0, v = 0; i <= COUNT; ++i, v += STEP) {
+ float x = FPBits(v).get_val();
+ if (FPBits(v).is_nan() || FPBits(v).is_inf())
+ continue;
+ libc_errno = 0;
+ EXPECT_FP_EQ_ROUNDING_MODE(
+ math::expf(x), static_rounding::expf(x, fenv_rounding), rounding);
+ }
+ }
+}
diff --git a/libc/test/src/math/smoke/CMakeLists.txt b/libc/test/src/math/smoke/CMakeLists.txt
index 97b4d927a0804..e03598440971d 100644
--- a/libc/test/src/math/smoke/CMakeLists.txt
+++ b/libc/test/src/math/smoke/CMakeLists.txt
@@ -1428,6 +1428,19 @@ add_fp_unittest(
libc.src.__support.FPUtil.fp_bits
)
+add_fp_unittest(
+ expf_static_rounding_test
+ SUITE
+ libc-math-smoke-tests
+ SRCS
+ expf_static_rounding_test.cpp
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.src.__support.math.expf
+ libc.src.__support.math.expf_integer_eval
+ libc.src.__support.FPUtil.fp_bits
+)
+
add_fp_unittest(
expf16_test
SUITE
diff --git a/libc/test/src/math/smoke/expf_static_rounding_test.cpp b/libc/test/src/math/smoke/expf_static_rounding_test.cpp
new file mode 100644
index 0000000000000..908f87714868c
--- /dev/null
+++ b/libc/test/src/math/smoke/expf_static_rounding_test.cpp
@@ -0,0 +1,62 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 smoke tests for static_rounding::expf(x)
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/math_macros.h"
+#include "hdr/stdint_proxy.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/math/expf.h"
+#include "src/__support/math/expf_integer_eval.h"
+#include "test/UnitTest/FPMatcher.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcExpfStaticRoundingTest = LIBC_NAMESPACE::testing::FPTest<float>;
+
+namespace static_rounding = LIBC_NAMESPACE::shared::math::static_rounding;
+namespace math = LIBC_NAMESPACE::math;
+
+TEST_F(LlvmLibcExpfStaticRoundingTest, SpecialNumbers) {
+ using LIBC_NAMESPACE::fputil::testing::get_fe_rounding;
+
+ constexpr float VALUES[] = {sNaN, aNaN, inf, neg_inf, 0.0f, -0.0f};
+
+ for (auto rounding : ROUNDING_MODES) {
+ const int fenv_rounding = get_fe_rounding(rounding);
+
+ for (auto x : VALUES) {
+ EXPECT_FP_EQ_ROUNDING_MODE(
+ math::expf(x), static_rounding::expf(x, fenv_rounding), rounding);
+ // Statically rounded expf doesn't raise exceptions, but the baseline
+ // expf may raise overflow exception.
+ // So, we won't check for that here.
+ }
+ }
+}
+
+TEST_F(LlvmLibcExpfStaticRoundingTest, Overflow) {
+ using LIBC_NAMESPACE::fputil::testing::get_fe_rounding;
+
+ constexpr float VALUES[] = {FPBits(0x7f7fffffU).get_val(),
+ FPBits(0x42cffff8U).get_val(),
+ FPBits(0x42d00008U).get_val()};
+
+ for (auto rounding : ROUNDING_MODES) {
+ const int fenv_rounding = get_fe_rounding(rounding);
+
+ for (auto x : VALUES) {
+ EXPECT_FP_EQ_ROUNDING_MODE(
+ math::expf(x), static_rounding::expf(x, fenv_rounding), rounding);
+ // The same reason above, in the SpecialNumbers smoke test suite
+ }
+ }
+}
More information about the libc-commits
mailing list