[libc-commits] [libc] 9dc80b9 - [libc][math] Add correctly rounded, float-only implementation of expf for GPUs (#231671)

via libc-commits libc-commits at lists.llvm.org
Thu Oct 15 09:51:05 PDT 2026


Author: Leandro Lacerda
Date: 2026-10-15T12:50:57-04:00
New Revision: 9dc80b90f8112ee0d371c46f65c12b012d453196

URL: https://github.com/llvm/llvm-project/commit/9dc80b90f8112ee0d371c46f65c12b012d453196
DIFF: https://github.com/llvm/llvm-project/commit/9dc80b90f8112ee0d371c46f65c12b012d453196.diff

LOG: [libc][math] Add correctly rounded, float-only implementation of expf for GPUs (#231671)

This patch adds a GPU-oriented implementation of `expf`, correctly
rounded for all inputs in round-to-nearest, ties-to-even mode. It uses
float-only intermediate arithmetic and Horner evaluation, without lookup
tables, a Ziv test, or separate fast and accurate paths.

It requires single-precision FMA.

### Performance on NVIDIA GeForce RTX 5070 (Cycles/Call)

Results were collected using a locally adapted `expf` benchmark based on
the GPU benchmark infrastructure in `libc/benchmarks/gpu`.

Tables report median cycles/call across five per-run means, with a
per-thread batch size of `N = 128`. Lower is better; the lowest value in
each row is bold. `Relative` compares FloatEvalCrRn (this PR) with the
fastest alternative in each row: “faster” means fewer cycles/call;
“slower” means more.

**Correctly rounded implementations in round-to-nearest, ties-to-even**

Both implementations are correctly rounded in this mode under the build
configuration below.

| Domain | DoubleEval | FloatEvalCrRn | Relative |
|---|---:|---:|---|
| CoreRange `[-10.0, 10.0]` | 532 | **212** | 2.51x faster |
| Normal `[-87.3, 88.7]` | 540 | **212** | 2.55x faster |
| Subnormal `[-103.0, -87.5]` | 626 | **212** | 2.95x faster |
| Finite `[-103.0, 88.7]` | 627 | **212** | 2.96x faster |
| Underflow `[-104.0, -103.0]` | 650 | **212** | 3.07x faster |
| Overflow `[88.0, 89.0]` | 532 | **212** | 2.51x faster |

**Float-only implementations**

Both use float-only intermediate arithmetic. FloatEval has no
correct-rounding guarantee; FloatEvalCrRn is correctly rounded in
round-to-nearest, ties-to-even.

| Domain | FloatEval | FloatEvalCrRn | Relative |
|---|---:|---:|---|
| CoreRange `[-10.0, 10.0]` | **175** | 212 | 1.21x slower |
| Normal `[-87.3, 88.7]` | **177** | 212 | 1.20x slower |
| Subnormal `[-103.0, -87.5]` | 295 | **212** | 1.39x faster |
| Finite `[-103.0, 88.7]` | 305 | **212** | 1.44x faster |
| Underflow `[-104.0, -103.0]` | 310 | **212** | 1.46x faster |
| Overflow `[88.0, 89.0]` | 222 | **212** | 1.05x faster |

**GPU-oriented, correctly rounded implementations in round-to-nearest,
ties-to-even**

All three use float-only intermediate arithmetic.

CoreMath128 and CoreMath1024 are Clang adaptations of the CUDA-enabled
variants from the CORE-MATH project
[[link](https://gitlab.inria.fr/core-math/core-math/-/blob/as_expf_floatonly/src/binary32/exp/expf.c)].
Their main lookup tables contain 128 and 1024 entries, respectively.
Both use a fast path with a Ziv test and an accurate path when the test
is inconclusive; both paths were enabled during benchmarking.

FloatEvalCrRn uses a single approximation path, without lookup tables or
a Ziv test.

| Domain | CoreMath128 | CoreMath1024 | FloatEvalCrRn | Relative |
|---|---:|---:|---:|---|
| CoreRange `[-10.0, 10.0]` | 251 | 364 | **212** | 1.18x faster |
| Normal `[-87.3, 88.7]` | 253 | 362 | **212** | 1.19x faster |
| Subnormal `[-103.0, -87.5]` | 254 | 367 | **212** | 1.20x faster |
| Finite `[-103.0, 88.7]` | 303 | 474 | **212** | 1.43x faster |
| Underflow `[-104.0, -103.0]` | 295 | 415 | **212** | 1.39x faster |
| Overflow `[88.0, 89.0]` | 318 | 444 | **212** | 1.50x faster |

**Environment and build configuration**

- GPU: NVIDIA GeForce RTX 5070, Blackwell, compute capability 12.0
(`sm_120`).
- CUDA Toolkit: 13.4 (`ptxas` 13.4.92).
- NVIDIA driver: 595.91.07.
- Compiler: Clang 24.0.0git, revision
`2d67f8878f55b5bcc6685f177aaa970c9dc7053a`.
- Compilation: `-O3`, LTO (`-flto`), `-march=sm_120`.
- Math configuration in `libc/config/gpu/config.json`:
`(LIBC_MATH_ASSUME_ROUND_NEAREST_ONLY |
LIBC_MATH_INTERMEDIATE_COMP_IN_FLOAT | LIBC_MATH_NO_ERRNO |
LIBC_MATH_NO_EXCEPT)`

---------

Assisted-by: Codex was used for performance analysis, code review, and
improvements to code comments.

---------

Signed-off-by: Leandro Augusto Lacerda Campos <leandrolcampos at yahoo.com.br>

Added: 
    libc/src/__support/math/expf_float_utils.h

Modified: 
    libc/benchmarks/gpu/CMakeLists.txt
    libc/src/__support/math/CMakeLists.txt
    libc/src/__support/math/expf.h
    libc/src/__support/math/expf_float_eval.h
    libc/test/src/math/CMakeLists.txt
    libc/test/src/math/exhaustive/expf_test.cpp
    libc/test/src/math/expf_test.cpp
    libc/test/src/math/smoke/CMakeLists.txt
    libc/test/src/math/smoke/expf_test.cpp

Removed: 
    


################################################################################
diff  --git a/libc/benchmarks/gpu/CMakeLists.txt b/libc/benchmarks/gpu/CMakeLists.txt
index c31a69c59db1c4..e7ad4f59bf7180 100644
--- a/libc/benchmarks/gpu/CMakeLists.txt
+++ b/libc/benchmarks/gpu/CMakeLists.txt
@@ -58,7 +58,7 @@ function(add_benchmark_framework_library name)
     target_compile_definitions(${name} PRIVATE TARGET_SUPPORTS_CLOCK)
   endif()
 
-  _get_hermetic_test_compile_options(compile_options "")
+  _get_hermetic_test_compile_options(compile_options "" "")
   target_include_directories(${name} PRIVATE ${LIBC_INCLUDE_DIR})
   target_compile_options(${name} PRIVATE ${compile_options} -nostdinc++)
 

diff  --git a/libc/src/__support/math/CMakeLists.txt b/libc/src/__support/math/CMakeLists.txt
index ef33e3f8ada7b0..d1ad4c9a76391c 100644
--- a/libc/src/__support/math/CMakeLists.txt
+++ b/libc/src/__support/math/CMakeLists.txt
@@ -3126,12 +3126,26 @@ add_header_library(
     libc.src.__support.macros.optimization
 )
 
+add_header_library(
+  expf_float_utils
+  HDRS
+    expf_float_utils.h
+  DEPENDS
+    libc.src.__support.FPUtil.double_double
+    libc.src.__support.FPUtil.fp_bits
+    libc.src.__support.FPUtil.multiply_add
+    libc.src.__support.FPUtil.polyeval
+    libc.src.__support.common
+    libc.src.__support.macros.config
+)
+
 add_header_library(
   expf_float_eval
   HDRS
     expf_float_eval.h
   DEPENDS
     .exp2f_float_utils
+    .expf_float_utils
     libc.src.__support.FPUtil.double_double
     libc.src.__support.FPUtil.fenv_impl
     libc.src.__support.FPUtil.fp_bits

diff  --git a/libc/src/__support/math/expf.h b/libc/src/__support/math/expf.h
index a64b323e08b2db..d53d19cfc78475 100644
--- a/libc/src/__support/math/expf.h
+++ b/libc/src/__support/math/expf.h
@@ -19,30 +19,41 @@
 #include "src/__support/macros/optimization.h"
 #include "src/__support/macros/properties/cpu_features.h"
 
-#if defined(LIBC_MATH_HAS_SKIP_ACCURATE_PASS) &&                               \
+#if defined(LIBC_TARGET_ARCH_IS_GPU) &&                                        \
+    !defined(LIBC_MATH_HAS_SKIP_ACCURATE_PASS) &&                              \
+    (defined(LIBC_MATH_HAS_SMALL_TABLES) ||                                    \
+     defined(LIBC_MATH_HAS_INTERMEDIATE_COMP_IN_FLOAT)) &&                     \
+    defined(LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY) &&                        \
+    defined(LIBC_MATH_HAS_NO_EXCEPT) && defined(LIBC_MATH_HAS_NO_ERRNO)
+
+#include "src/__support/math/expf_float_eval.h"
+#define LIBC_MATH_EXPF_IMPL float_eval::cr_expf_rn
+
+#elif defined(LIBC_MATH_HAS_SKIP_ACCURATE_PASS) &&                             \
     (defined(LIBC_MATH_HAS_SMALL_TABLES) ||                                    \
      defined(LIBC_MATH_HAS_INTERMEDIATE_COMP_IN_FLOAT))
 
 #include "src/__support/math/expf_float_eval.h"
-#define LIBC_MATH_EXPF_IMPL float_eval
+#define LIBC_MATH_EXPF_IMPL float_eval::expf
 
 #elif !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)
 
 #include "src/__support/math/expf_integer_eval.h"
-#define LIBC_MATH_EXPF_IMPL integer_eval
+#define LIBC_MATH_EXPF_IMPL integer_eval::expf
 
 #else
+
 #include "src/__support/math/expf_double_eval.h"
-#define LIBC_MATH_EXPF_IMPL double_eval
+#define LIBC_MATH_EXPF_IMPL double_eval::expf
 
-#endif // LIBC_MATH_HAS_SKIP_ACCURATE_PASS
+#endif
 
 namespace LIBC_NAMESPACE_DECL {
 namespace math {
 
-using LIBC_MATH_EXPF_IMPL::expf;
+LIBC_INLINE float expf(float x) { return LIBC_MATH_EXPF_IMPL(x); }
 
 } // namespace math
 } // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/__support/math/expf_float_eval.h b/libc/src/__support/math/expf_float_eval.h
index ab8ad649c539ca..ed7aabfd8f0d29 100644
--- a/libc/src/__support/math/expf_float_eval.h
+++ b/libc/src/__support/math/expf_float_eval.h
@@ -7,7 +7,7 @@
 //===----------------------------------------------------------------------===//
 ///
 /// \file
-/// Float-only implementation of expf.
+/// Float-only implementations of expf.
 ///
 //===----------------------------------------------------------------------===//
 
@@ -25,6 +25,7 @@
 #include "src/__support/macros/optimization.h"
 #include "src/__support/macros/properties/cpu_features.h"
 #include "src/__support/math/exp2f_float_utils.h"
+#include "src/__support/math/expf_float_utils.h"
 
 namespace LIBC_NAMESPACE_DECL {
 namespace math {
@@ -119,6 +120,75 @@ LIBC_INLINE float expf(float x) {
   return exp2f_eval(u, k);
 }
 
+// Designed for GPU performance. Correctly rounded only in round-to-nearest,
+// ties-to-even mode. Requires single-precision FMA. Does not support errno
+// or floating-point exception reporting.
+LIBC_INLINE float cr_expf_rn(float x) {
+  using FPBits = fputil::FPBits<float>;
+  FPBits x_bits(x);
+
+  uint32_t x_u = x_bits.uintval();
+  uint32_t x_abs_u = x_u & 0x7fff'ffffU;
+
+  // |x| <= 2^-25
+  bool is_tiny = x_abs_u <= 0x3300'0000U;
+
+  // x > log(2^128) or x is nan
+  bool should_overflow = x_u > 0x42b1'7217U && x_u < 0x8000'0000U;
+
+  // x <= log(2^-150) or x is nan
+  bool should_underflow = x_u >= 0xc2cf'f1b5U;
+
+  // x is nan with its sign bit set
+  bool is_neg_nan = should_underflow && x_bits.is_nan();
+
+  // log(2^-150) < x < log(2^-126)
+  bool produces_subnormal = x_u >= 0xc2ae'ac50U && !should_underflow;
+
+  // Use x_safe for range reduction to keep the conversion to int defined
+  float x_safe = (should_underflow || should_overflow) ? 0.0f : x;
+
+  // Range reduction:
+  //   k = round(x * log2(e))
+  //   x * log2(e) = k + u * log2(e)
+  //   e^x = 2^k * e^u
+  //
+  // Constants generated by Sollya with:
+  //   > display = hexadecimal!;
+  //   > LOG2_E = round(log2(exp(1)), SG, RN);
+  //   > LOG_2_HI = round(log(2), SG, RN);
+  //   > LOG_2_LO.hi = round(log(2) - LOG_2_HI, SG, RN);
+  //   > LOG_2_LO.lo = round(log(2) - LOG_2_HI - LOG_2_LO.hi, SG, RN);
+  constexpr float LOG2_E = 0x1.715476p+0f;
+  constexpr float LOG_2_HI = 0x1.62e43p-1f;
+  constexpr fputil::FloatFloat LOG_2_LO = {/*lo=*/-0x1.950d88p-54f,
+                                           /*hi=*/-0x1.05c61p-29f};
+
+  float kf = fputil::nearest_integer(x_safe * LOG2_E);
+  int k = static_cast<int>(kf);
+
+  float u_hi = fputil::multiply_add(-kf, LOG_2_HI, x_safe);
+  fputil::FloatFloat u_lo = fputil::quick_mult(-kf, LOG_2_LO);
+
+  fputil::FloatFloat u = fputil::exact_add(u_hi, u_lo.hi);
+  u.lo += u_lo.lo;
+
+  // Polynomial approximation: e^u
+  fputil::FloatFloat exp_u = cr_expf_rn_internal::exp_eval(u);
+
+  // Reconstruction: 2^k * e^u
+  float res = cr_expf_rn_internal::scale_result(exp_u, k, produces_subnormal);
+
+  // Special cases
+  constexpr float INF = FPBits::inf().get_val();
+
+  res = is_tiny ? 1.0f : res;
+  res = should_overflow ? x + INF : res;
+  res = should_underflow ? (is_neg_nan ? x : 0.0f) : res;
+
+  return res;
+}
+
 } // namespace float_eval
 } // namespace math
 } // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/__support/math/expf_float_utils.h b/libc/src/__support/math/expf_float_utils.h
new file mode 100644
index 00000000000000..d65abda27af380
--- /dev/null
+++ b/libc/src/__support/math/expf_float_utils.h
@@ -0,0 +1,149 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Utilities for float-only implementations of expf.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_MATH_EXPF_FLOAT_UTILS_H
+#define LLVM_LIBC_SRC___SUPPORT_MATH_EXPF_FLOAT_UTILS_H
+
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/FPUtil/PolyEval.h"
+#include "src/__support/FPUtil/double_double.h"
+#include "src/__support/FPUtil/multiply_add.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace math {
+namespace float_eval {
+
+namespace cr_expf_rn_internal {
+
+using fputil::FloatFloat;
+
+LIBC_ALWAYS_INLINE FloatFloat horner_step(float x, const FloatFloat &poly,
+                                          const FloatFloat &coeff) {
+  float hi = fputil::multiply_add(x, poly.hi, coeff.hi);
+  float lo = fputil::multiply_add(x, poly.hi, coeff.hi - hi);
+  lo = fputil::multiply_add(x, poly.lo, lo + coeff.lo);
+  return {lo, hi};
+}
+
+// Approximates exp(u) on [-log(2)/2, log(2)/2] with the degree-10
+// polynomial P(u) = 1 + u + u^2 * Q(u) using Horner evaluation.
+//
+// Coefficients generated by Sollya with:
+//   > prec = 165!;
+//   > display = hexadecimal!;
+//   > p = fpminimax(exp(x), [|2, ..., 10|],
+//                  [|49, 49, 49, 49, 49, 24, 24, 24, 24|],
+//                  [-log(2)/2; log(2)/2], relative,
+//                  floating, 1 + x);
+//
+// Float-float coefficients are split into:
+//   > hi = round(coeff(p, i), SG, RN);
+//   > lo = round(coeff(p, i) - hi, SG, RN);
+LIBC_INLINE FloatFloat exp_eval(const FloatFloat &u) {
+  // Coefficients of u^2, ..., u^6, in {lo, hi} order.
+  constexpr FloatFloat COEFFS[] = {
+      {-0x1.dp-46f, 0x1p-1f},
+      {-0x1.555edcp-28f, 0x1.555556p-3f},
+      {-0x1.54c28ep-30f, 0x1.555556p-5f},
+      {-0x1.b504e8p-32f, 0x1.111112p-7f},
+      {0x1.158eb2p-35f, 0x1.6c16cp-10f},
+  };
+
+  // The float-only tail contributes to degrees 7 through 10 of P.
+  float tail = fputil::polyeval(u.hi, 0x1.a0198ep-13f, 0x1.a01c0ap-16f,
+                                0x1.72e878p-19f, 0x1.2694cap-22f);
+  FloatFloat poly{0.0f, tail};
+  for (int i = 4; i >= 0; --i)
+    poly = horner_step(u.hi, poly, COEFFS[i]);
+
+  // Form expm1(u.hi) before adding 1, retaining its low component.
+  poly = horner_step(u.hi, poly, FloatFloat{0.0f, 1.0f});
+  FloatFloat expm1_u_hi = fputil::quick_mult(u.hi, poly);
+  FloatFloat exp_u = fputil::exact_add(1.0f, expm1_u_hi.hi);
+
+  // exp(u.hi + u.lo) = exp(u.hi) + u.lo * exp(u.hi) + O(u.lo^2).
+  //
+  // Combine the retained low part of expm1(u.hi) with the correction
+  // u.lo * exp(u.hi), approximating exp(u.hi) by exp_u.hi.
+  float corr = fputil::multiply_add(u.lo, exp_u.hi, expm1_u_hi.lo);
+
+  // Rounding low sums, exp_u.lo + corr, to nearest can create a midpoint for
+  // exp_u.hi + RN(exp_u.lo + corr).
+  //
+  // Round inexact low sums to odd whenever their rounded value is a power of
+  // two. This covers all such midpoint cases in this evaluation and preserves
+  // which side of the midpoint the exact sum lies on.
+  //
+  // FastTwoSum here is exact in RN: for nonzero corr, exp_u.lo is an integer
+  // multiple of ulp(corr), as required by Theorem 1 of "FastTwoSum revisited".
+  FloatFloat lo_sum = fputil::exact_add(exp_u.lo, corr);
+
+  uint32_t lo_sum_u = fputil::FPBits<float>(lo_sum.hi).uintval();
+  bool should_round_to_odd =
+      (lo_sum_u & fputil::FPBits<float>::FRACTION_MASK) == 0 &&
+      lo_sum.lo != 0.0f;
+  uint32_t rounding_adj =
+      ((lo_sum.hi > 0.0f) == (lo_sum.lo > 0.0f)) ? 1U : uint32_t(-1);
+  lo_sum_u += should_round_to_odd ? rounding_adj : 0U;
+  exp_u.lo = fputil::FPBits<float>(lo_sum_u).get_val();
+
+  return exp_u;
+}
+
+// Computes 2^k * (exp_u.hi + exp_u.lo).
+LIBC_INLINE float scale_result(const FloatFloat &exp_u, int k,
+                               bool produces_subnormal) {
+  using FPBits = fputil::FPBits<float>;
+
+  // Scaling the rounded sum by 2^k is exact for normal results: only the
+  // exponent changes. For subnormal results, the fixed spacing 2^-149 is
+  // coarser than the scaled spacing of the rounded sum. Scaling can then
+  // discard significand bits and round again.
+  //
+  // Choose B = 2^(-126 - k), so ulp(B) * 2^k = 2^-149. For inputs producing
+  // subnormals, B + exp_u.hi + exp_u.lo lies in B's binade [B, 2B). Rounding
+  // this biased sum therefore uses the desired spacing while staying normal.
+  // Use B = 0 for normal results to share the same arithmetic path.
+  uint32_t bias_u = produces_subnormal ? (1U - static_cast<uint32_t>(k))
+                                             << FPBits::FRACTION_LEN
+                                       : 0U;
+  float bias = FPBits(bias_u).get_val();
+
+  // For inputs producing subnormals, B >= exp_u.hi, so FastTwoSum retains
+  // the rounding residual exactly in RN. It is also exact when B = 0,
+  // reducing the final addition to RN(exp_u.hi + exp_u.lo).
+  FloatFloat sum_biased = fputil::exact_add(bias, exp_u.hi);
+  float sum_rounded = sum_biased.hi + (sum_biased.lo + exp_u.lo);
+  uint32_t res_u = FPBits(sum_rounded).uintval();
+
+  // For subnormal results, sum_rounded lies in B's binade [B, 2B):
+  // sum_rounded = B + M * ulp(B), where M is the integer encoded by
+  // its fraction field. Clearing the exponent makes the same fraction
+  // bits represent M * 2^-149 = (sum_rounded - B) * 2^k. This removes
+  // the bias and applies the scale without further rounding.
+  uint32_t res_mask = produces_subnormal ? FPBits::FRACTION_MASK : ~0U;
+  uint32_t exponent_adj = produces_subnormal ? 0U
+                                             : static_cast<uint32_t>(k)
+                                                   << FPBits::FRACTION_LEN;
+  return FPBits((res_u & res_mask) + exponent_adj).get_val();
+}
+
+} // namespace cr_expf_rn_internal
+
+} // namespace float_eval
+} // namespace math
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_MATH_EXPF_FLOAT_UTILS_H

diff  --git a/libc/test/src/math/CMakeLists.txt b/libc/test/src/math/CMakeLists.txt
index 92d599fba947e5..f33a9a3f109327 100644
--- a/libc/test/src/math/CMakeLists.txt
+++ b/libc/test/src/math/CMakeLists.txt
@@ -1345,7 +1345,6 @@ add_fp_unittest(
     libc.src.__support.FPUtil.fp_bits
 )
 
-
 add_fp_unittest(
   expf16_test
   NEED_MPFR

diff  --git a/libc/test/src/math/exhaustive/expf_test.cpp b/libc/test/src/math/exhaustive/expf_test.cpp
index 27aafd7d9ce7aa..c40029a79bdd76 100644
--- a/libc/test/src/math/exhaustive/expf_test.cpp
+++ b/libc/test/src/math/exhaustive/expf_test.cpp
@@ -54,6 +54,27 @@ TEST_F(LlvmLibcExpfFloatExhaustiveTest, NegativeRange) {
   test_full_range(mpfr::RoundingMode::Nearest, NEG_START, NEG_STOP);
 }
 
+#ifdef LIBC_TARGET_CPU_HAS_FMA_FLOAT
+// Float-eval implementation correctly rounded only in round-to-nearest,
+// ties-to-even mode: tested against the double-eval version in that mode
+// with a 0 ULP bound.
+static float cr_expf_rn(float x) {
+  return LIBC_NAMESPACE::math::float_eval::cr_expf_rn(x);
+}
+
+using LlvmLibcExpfFloatCrRnExhaustiveTest =
+    LlvmLibcUnaryOpAgainstBaselineExhaustiveMathTest<
+        float, LIBC_NAMESPACE::math::double_eval::expf, cr_expf_rn, 0>;
+
+TEST_F(LlvmLibcExpfFloatCrRnExhaustiveTest, PositiveRange) {
+  test_full_range(mpfr::RoundingMode::Nearest, POS_START, POS_STOP);
+}
+
+TEST_F(LlvmLibcExpfFloatCrRnExhaustiveTest, NegativeRange) {
+  test_full_range(mpfr::RoundingMode::Nearest, NEG_START, NEG_STOP);
+}
+#endif // LIBC_TARGET_CPU_HAS_FMA_FLOAT
+
 // Statically rounded implementation: tested against double_eval across all
 // roundings.
 using LlvmLibcExpfStaticRoundingExhaustiveTest =

diff  --git a/libc/test/src/math/expf_test.cpp b/libc/test/src/math/expf_test.cpp
index 2cf30559adabb7..eb2e1d4ea64614 100644
--- a/libc/test/src/math/expf_test.cpp
+++ b/libc/test/src/math/expf_test.cpp
@@ -180,6 +180,11 @@ LIST_EXPF_TESTS(DoubleEval, LIBC_NAMESPACE::math::double_eval::expf,
 LIST_EXPF_TESTS(FloatEval, LIBC_NAMESPACE::math::float_eval::expf,
                 /*ulp_tolerance=*/1.5, /*all_rounding=*/false,
                 /*check_exception_and_errno=*/true, /*check_errno=*/true)
+#ifdef LIBC_TARGET_CPU_HAS_FMA_FLOAT
+LIST_EXPF_TESTS(FloatEvalCrRn, LIBC_NAMESPACE::math::float_eval::cr_expf_rn,
+                /*ulp_tolerance=*/TOLERANCE + 0.5, /*all_rounding=*/false,
+                /*check_exception_and_errno=*/false, /*check_errno=*/false)
+#endif // LIBC_TARGET_CPU_HAS_FMA_FLOAT
 LIST_EXPF_TESTS(IntegerEval, LIBC_NAMESPACE::math::integer_eval::expf,
                 /*ulp_tolerance=*/0.5, /*all_rounding=*/false,
                 /*check_exception_and_errno=*/false, /*check_errno=*/false)

diff  --git a/libc/test/src/math/smoke/CMakeLists.txt b/libc/test/src/math/smoke/CMakeLists.txt
index fe9fafdfab9a3f..c00776e8e441ff 100644
--- a/libc/test/src/math/smoke/CMakeLists.txt
+++ b/libc/test/src/math/smoke/CMakeLists.txt
@@ -1444,7 +1444,6 @@ add_fp_unittest(
     libc.src.__support.FPUtil.fp_bits
 )
 
-
 add_fp_unittest(
   expf16_test
   SUITE

diff  --git a/libc/test/src/math/smoke/expf_test.cpp b/libc/test/src/math/smoke/expf_test.cpp
index f63d0d039fe414..df0e8767c95283 100644
--- a/libc/test/src/math/smoke/expf_test.cpp
+++ b/libc/test/src/math/smoke/expf_test.cpp
@@ -120,6 +120,11 @@ LIST_EXPF_TESTS(DoubleEval, LIBC_NAMESPACE::math::double_eval::expf,
 LIST_EXPF_TESTS(FloatEval, LIBC_NAMESPACE::math::float_eval::expf,
                 /*check_snan_invalid=*/false,
                 /*check_exception_and_errno=*/true, /*check_errno=*/true)
+#ifdef LIBC_TARGET_CPU_HAS_FMA_FLOAT
+LIST_EXPF_TESTS(FloatEvalCrRn, LIBC_NAMESPACE::math::float_eval::cr_expf_rn,
+                /*check_snan_invalid=*/false,
+                /*check_exception_and_errno=*/false, /*check_errno=*/false)
+#endif // LIBC_TARGET_CPU_HAS_FMA_FLOAT
 LIST_EXPF_TESTS(IntegerEval, LIBC_NAMESPACE::math::integer_eval::expf,
                 /*check_snan_invalid=*/false,
                 /*check_exception_and_errno=*/false, /*check_errno=*/false)


        


More information about the libc-commits mailing list