Skip to content

[libc][math][c23] Add exp10m1f C23 math function #87992

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Nov 7, 2024

Conversation

overmighty
Copy link
Member

Fixes #86503.

cc @lntue

Comment on lines +124 to +133
double dx = x;
double dx_sq = dx * dx;
double c0 = dx * Exp10Base::COEFFS[0];
double c1 =
fputil::multiply_add(dx, Exp10Base::COEFFS[2], Exp10Base::COEFFS[1]);
double c2 =
fputil::multiply_add(dx, Exp10Base::COEFFS[4], Exp10Base::COEFFS[3]);
// 10^dx - 1 ~ (1 + COEFFS[0] * dx + ... + COEFFS[4] * dx^5) - 1
// = COEFFS[0] * dx + ... + COEFFS[4] * dx^5
return static_cast<float>(fputil::polyeval(dx_sq, c0, c1, c2));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on Exp10Base::powb_lo.

@lntue lntue self-requested a review April 8, 2024 16:54
@llvmbot
Copy link
Member

llvmbot commented Apr 8, 2024

@llvm/pr-subscribers-libc

Author: OverMighty (overmighty)

Changes

Fixes #86503.

cc @lntue


Patch is 27.48 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/87992.diff

17 Files Affected:

  • (modified) libc/config/linux/x86_64/entrypoints.txt (+1)
  • (modified) libc/docs/math/index.rst (+1-1)
  • (modified) libc/spec/stdc.td (+2)
  • (modified) libc/src/math/CMakeLists.txt (+2)
  • (added) libc/src/math/exp10m1f.h (+18)
  • (modified) libc/src/math/generic/CMakeLists.txt (+22)
  • (added) libc/src/math/generic/exp10m1f.cpp (+215)
  • (modified) libc/test/src/math/CMakeLists.txt (+16)
  • (modified) libc/test/src/math/exhaustive/CMakeLists.txt (+15)
  • (added) libc/test/src/math/exhaustive/exp10m1f_test.cpp (+33)
  • (added) libc/test/src/math/exp10m1f_test.cpp (+85)
  • (modified) libc/test/src/math/exp2m1f_test.cpp (+10-11)
  • (modified) libc/test/src/math/smoke/CMakeLists.txt (+11)
  • (added) libc/test/src/math/smoke/exp10m1f_test.cpp (+56)
  • (modified) libc/test/src/math/smoke/exp2m1f_test.cpp (+4-6)
  • (modified) libc/utils/MPFRWrapper/MPFRUtils.cpp (+25)
  • (modified) libc/utils/MPFRWrapper/MPFRUtils.h (+1)
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 6bb53cb76220fc..616739392766f4 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -369,6 +369,7 @@ set(TARGET_LIBM_ENTRYPOINTS
     libc.src.math.expf
     libc.src.math.exp10
     libc.src.math.exp10f
+    libc.src.math.exp10m1f
     libc.src.math.exp2
     libc.src.math.exp2f
     libc.src.math.exp2m1f
diff --git a/libc/docs/math/index.rst b/libc/docs/math/index.rst
index 7a7b6c9c8db5de..ca8fd8e1eeee8e 100644
--- a/libc/docs/math/index.rst
+++ b/libc/docs/math/index.rst
@@ -266,7 +266,7 @@ Higher Math Functions
 +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
 | exp10     | |check|          | |check|         |                        |                      |                        | 7.12.6.2               | F.10.3.2                   |
 +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
-| exp10m1   |                  |                 |                        |                      |                        | 7.12.6.3               | F.10.3.3                   |
+| exp10m1   | |check|          |                 |                        |                      |                        | 7.12.6.3               | F.10.3.3                   |
 +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
 | exp2      | |check|          | |check|         |                        |                      |                        | 7.12.6.4               | F.10.3.4                   |
 +-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td
index 63d0449867114d..65812ccae45d0d 100644
--- a/libc/spec/stdc.td
+++ b/libc/spec/stdc.td
@@ -548,6 +548,8 @@ def StdC : StandardSpec<"stdc"> {
           FunctionSpec<"exp10", RetValSpec<DoubleType>, [ArgSpec<DoubleType>]>,
           FunctionSpec<"exp10f", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
 
+          FunctionSpec<"exp10m1f", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
+
           FunctionSpec<"remainderf", RetValSpec<FloatType>, [ArgSpec<FloatType>, ArgSpec<FloatType>]>,
           FunctionSpec<"remainder", RetValSpec<DoubleType>, [ArgSpec<DoubleType>, ArgSpec<DoubleType>]>,
           FunctionSpec<"remainderl", RetValSpec<LongDoubleType>, [ArgSpec<LongDoubleType>, ArgSpec<LongDoubleType>]>,
diff --git a/libc/src/math/CMakeLists.txt b/libc/src/math/CMakeLists.txt
index e8f699fabe3655..45b4dee0eb7291 100644
--- a/libc/src/math/CMakeLists.txt
+++ b/libc/src/math/CMakeLists.txt
@@ -93,6 +93,8 @@ add_math_entrypoint_object(exp2m1f)
 add_math_entrypoint_object(exp10)
 add_math_entrypoint_object(exp10f)
 
+add_math_entrypoint_object(exp10m1f)
+
 add_math_entrypoint_object(expm1)
 add_math_entrypoint_object(expm1f)
 
diff --git a/libc/src/math/exp10m1f.h b/libc/src/math/exp10m1f.h
new file mode 100644
index 00000000000000..e8a242eaba2cbd
--- /dev/null
+++ b/libc/src/math/exp10m1f.h
@@ -0,0 +1,18 @@
+//===-- Implementation header for exp10m1f ----------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_MATH_EXP10M1F_H
+#define LLVM_LIBC_SRC_MATH_EXP10M1F_H
+
+namespace LIBC_NAMESPACE {
+
+float exp10m1f(float x);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_MATH_EXP10M1F_H
diff --git a/libc/src/math/generic/CMakeLists.txt b/libc/src/math/generic/CMakeLists.txt
index afbcdea3cf7abb..7b09c93d042fcb 100644
--- a/libc/src/math/generic/CMakeLists.txt
+++ b/libc/src/math/generic/CMakeLists.txt
@@ -887,6 +887,7 @@ add_entrypoint_object(
     .explogxf
     libc.src.errno.errno
     libc.src.__support.common
+    libc.src.__support.FPUtil.except_value_utils
     libc.src.__support.FPUtil.fenv_impl
     libc.src.__support.FPUtil.fp_bits
     libc.src.__support.FPUtil.multiply_add
@@ -957,6 +958,27 @@ add_entrypoint_object(
     -O3
 )
 
+add_entrypoint_object(
+  exp10m1f
+  SRCS
+    exp10m1f.cpp
+  HDRS
+    ../exp10m1f.h
+  DEPENDS
+    .explogxf
+    libc.src.errno.errno
+    libc.src.__support.common
+    libc.src.__support.FPUtil.except_value_utils
+    libc.src.__support.FPUtil.fenv_impl
+    libc.src.__support.FPUtil.fp_bits
+    libc.src.__support.FPUtil.multiply_add
+    libc.src.__support.FPUtil.polyeval
+    libc.src.__support.FPUtil.rounding_mode
+    libc.src.__support.macros.optimization
+  COMPILE_OPTIONS
+    -O3
+)
+
 add_entrypoint_object(
   expm1
   SRCS
diff --git a/libc/src/math/generic/exp10m1f.cpp b/libc/src/math/generic/exp10m1f.cpp
new file mode 100644
index 00000000000000..50d4c50fb869ca
--- /dev/null
+++ b/libc/src/math/generic/exp10m1f.cpp
@@ -0,0 +1,215 @@
+//===-- Implementation of exp10m1f function -------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/math/exp10m1f.h"
+#include "src/__support/FPUtil/FEnvImpl.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/FPUtil/PolyEval.h"
+#include "src/__support/FPUtil/except_value_utils.h"
+#include "src/__support/FPUtil/multiply_add.h"
+#include "src/__support/FPUtil/rounding_mode.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/optimization.h"
+#include "src/errno/libc_errno.h"
+
+#include "explogxf.h"
+
+namespace LIBC_NAMESPACE {
+
+static constexpr size_t N_EXCEPTS_LO = 11;
+
+static constexpr fputil::ExceptValues<float, N_EXCEPTS_LO> EXP10M1F_EXCEPTS_LO =
+    {{
+        // x = 0x1.0fe54ep-11, exp10m1f(x) = 0x1.3937eep-10 (RZ)
+        {0x3a07'f2a7U, 0x3a9c'9bf7U, 1U, 0U, 1U},
+        // x = 0x1.80e6eap-11, exp10m1f(x) = 0x1.bb8272p-10 (RZ)
+        {0x3a40'7375U, 0x3add'c139U, 1U, 0U, 1U},
+        // x = -0x1.2a33bcp-51, exp10m1f(x) = -0x1.57515ep-50 (RZ)
+        {0xa615'19deU, 0xa6ab'a8afU, 0U, 1U, 0U},
+        // x = -0x0p+0, exp10m1f(x) = -0x0p+0 (RZ)
+        {0x8000'0000U, 0x8000'0000U, 0U, 0U, 0U},
+        // x = -0x1.b59e08p-31, exp10m1f(x) = -0x1.f7d356p-30 (RZ)
+        {0xb05a'cf04U, 0xb0fb'e9abU, 0U, 1U, 1U},
+        // x = -0x1.bf342p-12, exp10m1f(x) = -0x1.014e02p-10 (RZ)
+        {0xb9df'9a10U, 0xba80'a701U, 0U, 1U, 0U},
+        // x = -0x1.6207fp-11, exp10m1f(x) = -0x1.9746cap-10 (RZ)
+        {0xba31'03f8U, 0xbacb'a365U, 0U, 1U, 1U},
+        // x = -0x1.bd0c66p-11, exp10m1f(x) = -0x1.ffe168p-10 (RZ)
+        {0xba5e'8633U, 0xbaff'f0b4U, 0U, 1U, 1U},
+        // x = -0x1.ffd84cp-10, exp10m1f(x) = -0x1.25faf2p-8 (RZ)
+        {0xbaff'ec26U, 0xbb92'fd79U, 0U, 1U, 0U},
+        // x = -0x1.a74172p-9, exp10m1f(x) = -0x1.e57be2p-8 (RZ)
+        {0xbb53'a0b9U, 0xbbf2'bdf1U, 0U, 1U, 1U},
+        // x = -0x1.cb694cp-9, exp10m1f(x) = -0x1.0764e4p-7 (RZ)
+        {0xbb65'b4a6U, 0xbc03'b272U, 0U, 1U, 0U},
+    }};
+
+static constexpr size_t N_EXCEPTS_HI = 19;
+
+static constexpr fputil::ExceptValues<float, N_EXCEPTS_HI> EXP10M1F_EXCEPTS_HI =
+    {{
+        // (input, RZ output, RU offset, RD offset, RN offset)
+        // x = 0x1.8d31eep-8, exp10m1f(x) = 0x1.cc7e4cp-7 (RZ)
+        {0x3bc6'98f7U, 0x3c66'3f26U, 1U, 0U, 1U},
+        // x = 0x1.915fcep-8, exp10m1f(x) = 0x1.d15f72p-7 (RZ)
+        {0x3bc8'afe7U, 0x3c68'afb9U, 1U, 0U, 0U},
+        // x = 0x1.bcf982p-8, exp10m1f(x) = 0x1.022928p-6 (RZ)
+        {0x3bde'7cc1U, 0x3c81'1494U, 1U, 0U, 1U},
+        // x = 0x1.99ff0ap-7, exp10m1f(x) = 0x1.dee416p-6 (RZ)
+        {0x3c4c'ff85U, 0x3cef'720bU, 1U, 0U, 0U},
+        // x = 0x1.75ea14p-6, exp10m1f(x) = 0x1.b9ff16p-5 (RZ)
+        {0x3cba'f50aU, 0x3d5c'ff8bU, 1U, 0U, 0U},
+        // x = 0x1.f81b64p-6, exp10m1f(x) = 0x1.2cb6bcp-4 (RZ)
+        {0x3cfc'0db2U, 0x3d96'5b5eU, 1U, 0U, 0U},
+        // x = 0x1.fafecp+3, exp10m1f(x) = 0x1.8c880ap+52 (RZ)
+        {0x417d'7f60U, 0x59c6'4405U, 1U, 0U, 0U},
+        // x = -0x1.3bf094p-8, exp10m1f(x) = -0x1.69ba4ap-7 (RZ)
+        {0xbb9d'f84aU, 0xbc34'dd25U, 0U, 1U, 0U},
+        // x = -0x1.4558bcp-8, exp10m1f(x) = -0x1.746fb8p-7 (RZ)
+        {0xbba2'ac5eU, 0xbc3a'37dcU, 0U, 1U, 1U},
+        // x = -0x1.4bb43p-8, exp10m1f(x) = -0x1.7babe4p-7 (RZ)
+        {0xbba5'da18U, 0xbc3d'd5f2U, 0U, 1U, 1U},
+        // x = -0x1.776cc8p-8, exp10m1f(x) = -0x1.ad62c4p-7 (RZ)
+        {0xbbbb'b664U, 0xbc56'b162U, 0U, 1U, 0U},
+        // x = -0x1.f024cp-8, exp10m1f(x) = -0x1.1b20d6p-6 (RZ)
+        {0xbbf8'1260U, 0xbc8d'906bU, 0U, 1U, 1U},
+        // x = -0x1.f510eep-8, exp10m1f(x) = -0x1.1de9aap-6 (RZ)
+        {0xbbfa'8877U, 0xbc8e'f4d5U, 0U, 1U, 0U},
+        // x = -0x1.0b43c4p-7, exp10m1f(x) = -0x1.30d418p-6 (RZ)
+        {0xbc05'a1e2U, 0xbc98'6a0cU, 0U, 1U, 0U},
+        // x = -0x1.245ee4p-7, exp10m1f(x) = -0x1.4d2b86p-6 (RZ)
+        {0xbc12'2f72U, 0xbca6'95c3U, 0U, 1U, 0U},
+        // x = -0x1.f9f2dap-7, exp10m1f(x) = -0x1.1e2186p-5 (RZ)
+        {0xbc7c'f96dU, 0xbd0f'10c3U, 0U, 1U, 0U},
+        // x = -0x1.08e42p-6, exp10m1f(x) = -0x1.2b5c4p-5 (RZ)
+        {0xbc84'7210U, 0xbd15'ae20U, 0U, 1U, 1U},
+        // x = -0x1.0cdc44p-5, exp10m1f(x) = -0x1.2a2152p-4 (RZ)
+        {0xbd06'6e22U, 0xbd95'10a9U, 0U, 1U, 1U},
+        // x = -0x1.ca4322p-5, exp10m1f(x) = -0x1.ef073p-4 (RZ)
+        {0xbd65'2191U, 0xbdf7'8398U, 0U, 1U, 1U},
+    }};
+
+LLVM_LIBC_FUNCTION(float, exp10m1f, (float x)) {
+  using FPBits = fputil::FPBits<float>;
+  FPBits xbits(x);
+
+  uint32_t x_u = xbits.uintval();
+  uint32_t x_abs = x_u & 0x7fff'ffffU;
+
+  // When x >= log10(2^128), or x is nan
+  if (LIBC_UNLIKELY(xbits.is_pos() && x_u >= 0x421a'209bU)) {
+    if (xbits.is_finite()) {
+      int rounding = fputil::quick_get_round();
+      if (rounding == FE_DOWNWARD || rounding == FE_TOWARDZERO)
+        return FPBits::max_normal().get_val();
+
+      fputil::set_errno_if_required(ERANGE);
+      fputil::raise_except_if_required(FE_OVERFLOW);
+    }
+
+    // x >= log10(2^128) and 10^x - 1 rounds to +inf, or x is +inf or nan
+    return x + FPBits::inf().get_val();
+  }
+
+  // When |x| <= log10(2) * 2^(-6)
+  if (LIBC_UNLIKELY(x_abs <= 0x3b9a'209bU)) {
+    if (auto r = EXP10M1F_EXCEPTS_LO.lookup(x_u); LIBC_UNLIKELY(r.has_value()))
+      return r.value();
+
+    double dx = x;
+    double dx_sq = dx * dx;
+    double c0 = dx * Exp10Base::COEFFS[0];
+    double c1 =
+        fputil::multiply_add(dx, Exp10Base::COEFFS[2], Exp10Base::COEFFS[1]);
+    double c2 =
+        fputil::multiply_add(dx, Exp10Base::COEFFS[4], Exp10Base::COEFFS[3]);
+    // 10^dx - 1 ~ (1 + COEFFS[0] * dx + ... + COEFFS[4] * dx^5) - 1
+    //           = COEFFS[0] * dx + ... + COEFFS[4] * dx^5
+    return static_cast<float>(fputil::polyeval(dx_sq, c0, c1, c2));
+  }
+
+  // When x <= log10(2^-25), or x is nan
+  if (LIBC_UNLIKELY(x <= -0x1.e1a5e2p+2f)) {
+    // exp10m1(-inf) = -1
+    if (xbits.is_inf())
+      return -1.0f;
+    // exp10m1(nan) = nan
+    if (xbits.is_nan())
+      return x;
+
+    int rounding = fputil::quick_get_round();
+    if (rounding == FE_UPWARD || rounding == FE_TOWARDZERO ||
+        (rounding == FE_TONEAREST && x == -0x1.e1a5e2p+2f))
+      return -0x1.ffff'fep-1f; // -1.0f + 0x1.0p-24f
+
+    fputil::set_errno_if_required(ERANGE);
+    fputil::raise_except_if_required(FE_UNDERFLOW);
+    return -1.0f;
+  }
+
+  // Exact outputs when x = 1, 2, ..., 10.
+  // Quick check mask: 0x800f'ffffU = ~(bits of 1.0f | ... | bits of 10.0f)
+  if (LIBC_UNLIKELY((x_u & 0x800f'ffffU) == 0)) {
+    switch (x_u) {
+    case 0x3f800000U: // x = 1.0f
+      return 9.0f;
+    case 0x40000000U: // x = 2.0f
+      return 99.0f;
+    case 0x40400000U: // x = 3.0f
+      return 999.0f;
+    case 0x40800000U: // x = 4.0f
+      return 9'999.0f;
+    case 0x40a00000U: // x = 5.0f
+      return 99'999.0f;
+    case 0x40c00000U: // x = 6.0f
+      return 999'999.0f;
+    case 0x40e00000U: // x = 7.0f
+      return 9'999'999.0f;
+    case 0x41000000U: { // x = 8.0f
+      int rounding = fputil::quick_get_round();
+      if (rounding == FE_UPWARD || rounding == FE_TONEAREST)
+        return 100'000'000.0f;
+      return 99'999'992.0f;
+    }
+    case 0x41100000U: { // x = 9.0f
+      int rounding = fputil::quick_get_round();
+      if (rounding == FE_UPWARD || rounding == FE_TONEAREST)
+        return 1'000'000'000.0f;
+      return 999'999'936.0f;
+    }
+    case 0x41200000U: { // x = 10.0f
+      int rounding = fputil::quick_get_round();
+      if (rounding == FE_UPWARD || rounding == FE_TONEAREST)
+        return 10'000'000'000.0f;
+      return 9'999'998'976.0f;
+    }
+    }
+  }
+
+  if (auto r = EXP10M1F_EXCEPTS_HI.lookup(x_u); LIBC_UNLIKELY(r.has_value()))
+    return r.value();
+
+  // Range reduction: 10^x = 2^(mid + hi) * 10^lo
+  //   rr = (2^(mid + hi), lo)
+  auto rr = exp_b_range_reduc<Exp10Base>(x);
+
+  // The low part is approximated by a degree-5 minimax polynomial.
+  // 10^lo ~ 1 + COEFFS[0] * lo + ... + COEFFS[4] * lo^5
+  double lo_sq = rr.lo * rr.lo;
+  double c0 = fputil::multiply_add(rr.lo, Exp10Base::COEFFS[0], 1.0);
+  double c1 =
+      fputil::multiply_add(rr.lo, Exp10Base::COEFFS[2], Exp10Base::COEFFS[1]);
+  double c2 =
+      fputil::multiply_add(rr.lo, Exp10Base::COEFFS[4], Exp10Base::COEFFS[3]);
+  double exp10_lo = fputil::polyeval(lo_sq, c0, c1, c2);
+  // 10^x - 1 = 2^(mid + hi) * 10^lo - 1
+  //          ~ mh * exp10_lo - 1
+  return static_cast<float>(fputil::multiply_add(exp10_lo, rr.mh, -1.0));
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/test/src/math/CMakeLists.txt b/libc/test/src/math/CMakeLists.txt
index 01416eefc15ef8..ff5f3631ee7d7f 100644
--- a/libc/test/src/math/CMakeLists.txt
+++ b/libc/test/src/math/CMakeLists.txt
@@ -653,6 +653,7 @@ add_fp_unittest(
   SRCS
     exp2m1f_test.cpp
   DEPENDS
+    libc.hdr.math_macros
     libc.src.errno.errno
     libc.src.math.exp2m1f
     libc.src.__support.CPP.array
@@ -685,6 +686,21 @@ add_fp_unittest(
    libc.src.__support.FPUtil.fp_bits
 )
 
+add_fp_unittest(
+  exp10m1f_test
+  NEED_MPFR
+  SUITE
+    libc-math-unittests
+  SRCS
+    exp10m1f_test.cpp
+  DEPENDS
+    libc.hdr.math_macros
+    libc.src.errno.errno
+    libc.src.math.exp10m1f
+    libc.src.__support.CPP.array
+    libc.src.__support.FPUtil.fp_bits
+)
+
 add_fp_unittest(
   copysign_test
   SUITE
diff --git a/libc/test/src/math/exhaustive/CMakeLists.txt b/libc/test/src/math/exhaustive/CMakeLists.txt
index 938e519aff0847..8f0c0d4bc66f3d 100644
--- a/libc/test/src/math/exhaustive/CMakeLists.txt
+++ b/libc/test/src/math/exhaustive/CMakeLists.txt
@@ -165,6 +165,21 @@ add_fp_unittest(
     -lpthread
 )
 
+add_fp_unittest(
+  exp10m1f_test
+  NO_RUN_POSTBUILD
+  NEED_MPFR
+  SUITE
+    libc_math_exhaustive_tests
+  SRCS
+    exp10m1f_test.cpp
+  DEPENDS
+    .exhaustive_test
+    libc.src.math.exp10m1f
+  LINK_LIBRARIES
+    -lpthread
+)
+
 add_fp_unittest(
   expm1f_test
   NO_RUN_POSTBUILD
diff --git a/libc/test/src/math/exhaustive/exp10m1f_test.cpp b/libc/test/src/math/exhaustive/exp10m1f_test.cpp
new file mode 100644
index 00000000000000..b9b2290f8b570d
--- /dev/null
+++ b/libc/test/src/math/exhaustive/exp10m1f_test.cpp
@@ -0,0 +1,33 @@
+//===-- Exhaustive test for exp10m1f --------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "exhaustive_test.h"
+#include "src/math/exp10m1f.h"
+#include "utils/MPFRWrapper/MPFRUtils.h"
+
+namespace mpfr = LIBC_NAMESPACE::testing::mpfr;
+
+using LlvmLibcExp10m1fExhaustiveTest =
+    LlvmLibcUnaryOpExhaustiveMathTest<float, mpfr::Operation::Exp10m1,
+                                      LIBC_NAMESPACE::exp10m1f>;
+
+// Range: [0, Inf];
+static constexpr uint32_t POS_START = 0x0000'0000U;
+static constexpr uint32_t POS_STOP = 0x7f80'0000U;
+
+TEST_F(LlvmLibcExp10m1fExhaustiveTest, PostiveRange) {
+  test_full_range_all_roundings(POS_START, POS_STOP);
+}
+
+// Range: [-Inf, 0];
+static constexpr uint32_t NEG_START = 0x8000'0000U;
+static constexpr uint32_t NEG_STOP = 0xff80'0000U;
+
+TEST_F(LlvmLibcExp10m1fExhaustiveTest, NegativeRange) {
+  test_full_range_all_roundings(NEG_START, NEG_STOP);
+}
diff --git a/libc/test/src/math/exp10m1f_test.cpp b/libc/test/src/math/exp10m1f_test.cpp
new file mode 100644
index 00000000000000..946b7b4db9280c
--- /dev/null
+++ b/libc/test/src/math/exp10m1f_test.cpp
@@ -0,0 +1,85 @@
+//===-- Unittests for exp10m1f --------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "hdr/math_macros.h"
+#include "src/__support/CPP/array.h"
+#include "src/errno/libc_errno.h"
+#include "src/math/exp10m1f.h"
+#include "test/UnitTest/FPMatcher.h"
+#include "test/UnitTest/Test.h"
+#include "utils/MPFRWrapper/MPFRUtils.h"
+
+#include <stdint.h>
+
+using LlvmLibcExp10m1fTest = LIBC_NAMESPACE::testing::FPTest<float>;
+
+namespace mpfr = LIBC_NAMESPACE::testing::mpfr;
+
+TEST_F(LlvmLibcExp10m1fTest, TrickyInputs) {
+  constexpr LIBC_NAMESPACE::cpp::array<float, 30> INPUTS = {
+      // EXP10M1F_EXCEPTS_LO
+      0x1.0fe54ep-11f,
+      0x1.80e6eap-11f,
+      -0x1.2a33bcp-51f,
+      -0x0p+0f,
+      -0x1.b59e08p-31f,
+      -0x1.bf342p-12f,
+      -0x1.6207fp-11f,
+      -0x1.bd0c66p-11f,
+      -0x1.ffd84cp-10f,
+      -0x1.a74172p-9f,
+      -0x1.cb694cp-9f,
+      // EXP10M1F_EXCEPTS_HI
+      0x1.8d31eep-8f,
+      0x1.915fcep-8f,
+      0x1.bcf982p-8f,
+      0x1.99ff0ap-7f,
+      0x1.75ea14p-6f,
+      0x1.f81b64p-6f,
+      0x1.fafecp+3f,
+      -0x1.3bf094p-8f,
+      -0x1.4558bcp-8f,
+      -0x1.4bb43p-8f,
+      -0x1.776cc8p-8f,
+      -0x1.f024cp-8f,
+      -0x1.f510eep-8f,
+      -0x1.0b43c4p-7f,
+      -0x1.245ee4p-7f,
+      -0x1.f9f2dap-7f,
+      -0x1.08e42p-6f,
+      -0x1.0cdc44p-5f,
+      -0x1.ca4322p-5f,
+  };
+
+  for (float x : INPUTS) {
+    LIBC_NAMESPACE::libc_errno = 0;
+    EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Exp10m1, x,
+                                   LIBC_NAMESPACE::exp10m1f(x), 0.5);
+  }
+}
+
+TEST_F(LlvmLibcExp10m1fTest, InFloatRange) {
+  constexpr uint32_t COUNT = 100'000;
+  constexpr uint32_t STEP = UINT32_MAX / COUNT;
+  for (uint32_t i = 0, v = 0; i <= COUNT; ++i, v += STEP) {
+    float x = FPBits(v).get_val();
+    if (isnan(x) || isinf(x))
+      continue;
+    LIBC_NAMESPACE::libc_errno = 0;
+    float result = LIBC_NAMESPACE::exp10m1f(x);
+
+    // If the computation resulted in an error or did not produce valid result
+    // in the single-precision floating point range, then ignore comparing with
+    // MPFR result as MPFR can still produce valid results because of its
+    // wider precision.
+    if (isnan(result) || isinf(result) || LIBC_NAMESPACE::libc_errno != 0)
+      continue;
+    ASSERT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Exp10m1, x,
+                                   LIBC_NAMESPACE::exp10m1f(x), 0.5);
+  }
+}
diff --git a/libc/test/src/math/exp2m1f_test.cpp b/libc/test/src/math/exp2m1f_test.cpp
index cb948289b6179c..46031b546c37aa 100644
--- a/libc/test/src/math/exp2m1f_test.cpp
+++ b/libc/test/src/math/exp2m1f_test.cpp
@@ -8,7 +8,6 @@
 
 #include "hdr/math_macros.h"
 #include "src/__support/CPP/array.h"
-#include "src/__support/FPUtil/FPBits.h"
 #include "src/errno/libc_errno.h"
 ...
[truncated]

Comment on lines +21 to +22
EXPECT_EQ(FPBits(neg_aNaN).uintval(),
FPBits(LIBC_NAMESPACE::exp10m1f(neg_aNaN)).uintval());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my x86-64 machine, this new neg_aNaN test was unluckily still succeeding when exp10m1f was not taking the if (xbits.is_nan()) branch. The NaN double changes through the computations and the cast of the final result to float gives the same value as neg_aNaN.

I'm not sure when I should just use the unary - operator and when I should add new constants in FPMatcher.h. In this case, I didn't find a definition of the behavior of -aNaN and I'm not sure if it will work as expected on all architectures, so I added a new constant for FPBits::quiet_nan(Sign::NEG). In the Underflow test below, I used -max_normal instead of defining neg_max_normal again like I did in DECLARE_SPECIAL_CONSTANTS when I implemented fromfp.

@overmighty overmighty marked this pull request as ready for review April 10, 2024 00:36
Copy link
Contributor

@michaelrj-google michaelrj-google left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good from a code style perspective, but final math review will probably have to wait until @lntue is back.

Thanks for making this patch!

@overmighty
Copy link
Member Author

overmighty commented Apr 29, 2024

Rebased.

Just noticed that UBSan throws an error when running libc.test.src.math.exp10m1f_test.__unit__:

/home/overmighty/projects/llvm-project/libc/src/math/generic/explogxf.h:164:20: runtime error: left shift of negative value -25

@jhuber6
Copy link
Contributor

jhuber6 commented Apr 29, 2024

UBSan throws an error when running libc.test.src.math.exp10m1f_test.__unit__:

/home/overmighty/projects/llvm-project/libc/src/math/generic/explogxf.h:164:20: runtime error: left shift of negative value -25

Shifting by anything outside of [0, n] for a 2^n integer type is UB. We'd probably need to clamp the result, or just find where it goes negative and make it stop doing that.

@nickdesaulniers
Copy link
Member

nickdesaulniers commented Oct 15, 2024

What's the status of this PR? Looks like there's now some merge conflicts.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
Undo changes to exp2m1f build and tests.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
Fix left shift of negative values.
@overmighty
Copy link
Member Author

What's the status of this PR? Looks like there's now some merge conflicts.

It's ready for review again.

cc @lntue

@overmighty overmighty merged commit faefedf into llvm:main Nov 7, 2024
8 checks passed
Groverkss pushed a commit to iree-org/llvm-project that referenced this pull request Nov 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[libc][math][c23] Implement C23 math function exp10m1f
6 participants