[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