Skip to content

Commit 79c6351

Browse files
committed
[libc][math][c23] Add exp2m1f C23 math function
Fixes #86502.
1 parent 4078763 commit 79c6351

File tree

12 files changed

+314
-0
lines changed

12 files changed

+314
-0
lines changed

libc/config/linux/x86_64/entrypoints.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ set(TARGET_LIBM_ENTRYPOINTS
370370
libc.src.math.exp10f
371371
libc.src.math.exp2
372372
libc.src.math.exp2f
373+
libc.src.math.exp2m1f
373374
libc.src.math.expm1
374375
libc.src.math.expm1f
375376
libc.src.math.fabs

libc/spec/stdc.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,8 @@ def StdC : StandardSpec<"stdc"> {
535535
FunctionSpec<"exp2", RetValSpec<DoubleType>, [ArgSpec<DoubleType>]>,
536536
FunctionSpec<"exp2f", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
537537

538+
FunctionSpec<"exp2m1f", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
539+
538540
FunctionSpec<"expm1", RetValSpec<DoubleType>, [ArgSpec<DoubleType>]>,
539541
FunctionSpec<"expm1f", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
540542

libc/src/math/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ add_math_entrypoint_object(expf)
8888
add_math_entrypoint_object(exp2)
8989
add_math_entrypoint_object(exp2f)
9090

91+
add_math_entrypoint_object(exp2m1f)
92+
9193
add_math_entrypoint_object(exp10)
9294
add_math_entrypoint_object(exp10f)
9395

libc/src/math/exp2m1f.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//===-- Implementation header for exp2m1f -----------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_LIBC_SRC_MATH_EXP2M1F_H
10+
#define LLVM_LIBC_SRC_MATH_EXP2M1F_H
11+
12+
namespace LIBC_NAMESPACE {
13+
14+
float exp2m1f(float x);
15+
16+
} // namespace LIBC_NAMESPACE
17+
18+
#endif // LLVM_LIBC_SRC_MATH_EXP2M1F_H

libc/src/math/generic/CMakeLists.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,27 @@ add_entrypoint_object(
837837
-O3
838838
)
839839

840+
add_entrypoint_object(
841+
exp2m1f
842+
SRCS
843+
exp2m1f.cpp
844+
HDRS
845+
../exp2m1f.h
846+
DEPENDS
847+
.explogxf
848+
libc.src.errno.errno
849+
libc.src.__support.common
850+
libc.src.__support.FPUtil.fenv_impl
851+
libc.src.__support.FPUtil.fp_bits
852+
libc.src.__support.FPUtil.multiply_add
853+
libc.src.__support.FPUtil.polyeval
854+
libc.src.__support.FPUtil.rounding_mode
855+
libc.src.__support.macros.optimization
856+
libc.src.__support.macros.properties.cpu_features
857+
COMPILE_OPTIONS
858+
-O3
859+
)
860+
840861
add_entrypoint_object(
841862
exp10
842863
SRCS

libc/src/math/generic/exp2m1f.cpp

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//===-- Implementation of exp2m1f function --------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "src/math/exp2m1f.h"
10+
#include "src/__support/FPUtil/FEnvImpl.h"
11+
#include "src/__support/FPUtil/FPBits.h"
12+
#include "src/__support/FPUtil/PolyEval.h"
13+
#include "src/__support/FPUtil/multiply_add.h"
14+
#include "src/__support/FPUtil/rounding_mode.h"
15+
#include "src/__support/common.h"
16+
#include "src/__support/macros/optimization.h"
17+
#include "src/__support/macros/properties/cpu_features.h"
18+
#include "src/errno/libc_errno.h"
19+
20+
#include "explogxf.h"
21+
22+
namespace LIBC_NAMESPACE {
23+
24+
LLVM_LIBC_FUNCTION(float, exp2m1f, (float x)) {
25+
using FPBits = fputil::FPBits<float>;
26+
FPBits xbits(x);
27+
28+
uint32_t x_u = xbits.uintval();
29+
uint32_t x_abs = x_u & 0x7fff'ffffU;
30+
31+
// When |x| >= 128, or x is nan, or |x| <= 2^-5
32+
if (LIBC_UNLIKELY(x_abs >= 0x4300'0000U || x_abs <= 0x3d00'0000U)) {
33+
// |x| <= 2^-5
34+
if (x_abs <= 0x3d00'0000) {
35+
// TODO: Handle exceptional values.
36+
37+
// Minimax polynomial generated by Sollya with:
38+
// > display = hexadecimal;
39+
// > fpminimax((2^x - 1)/x, 5, [|D...|], [-2^-5, 2^-5]);
40+
constexpr double COEFFS[] = {
41+
0x1.62e42fefa39f3p-1, 0x1.ebfbdff82c57bp-3, 0x1.c6b08d6f2d7aap-5,
42+
0x1.3b2ab6fc92f5dp-7, 0x1.5d897cfe27125p-10, 0x1.43090e61e6af1p-13};
43+
double xd = x;
44+
double xsq = xd * xd;
45+
double c0 = fputil::multiply_add(xd, COEFFS[1], COEFFS[0]);
46+
double c1 = fputil::multiply_add(xd, COEFFS[3], COEFFS[2]);
47+
double c2 = fputil::multiply_add(xd, COEFFS[5], COEFFS[4]);
48+
double p = fputil::polyeval(xsq, c0, c1, c2);
49+
return static_cast<float>(p * xd);
50+
}
51+
52+
// x >= 128, or x is nan
53+
if (xbits.is_pos()) {
54+
if (xbits.is_finite()) {
55+
int rounding = fputil::quick_get_round();
56+
if (rounding == FE_DOWNWARD || rounding == FE_TOWARDZERO)
57+
return FPBits::max_normal().get_val();
58+
59+
fputil::set_errno_if_required(ERANGE);
60+
fputil::raise_except_if_required(FE_OVERFLOW);
61+
}
62+
63+
// x >= 128 and 2^x - 1 rounds to +inf, or x is +inf or nan
64+
return x + FPBits::inf().get_val();
65+
}
66+
67+
// x <= -25
68+
if (x <= -25.0f) {
69+
// 2^(-inf) - 1 = -1
70+
if (xbits.is_inf())
71+
return -1.0f;
72+
// 2^nan - 1 = nan
73+
if (xbits.is_nan())
74+
return x;
75+
76+
int rounding = fputil::quick_get_round();
77+
if (rounding == FE_UPWARD || rounding == FE_TOWARDZERO)
78+
return -0x1.ffff'fep-1f; // -1.0f + 0x1.0p-24f
79+
80+
fputil::set_errno_if_required(ERANGE);
81+
fputil::raise_except_if_required(FE_UNDERFLOW);
82+
return -1.0f;
83+
}
84+
}
85+
86+
// For -25 < x < 128, to compute 2^x, we perform the following range
87+
// reduction: find hi, mid, lo such that:
88+
// x = hi + mid + lo, in which:
89+
// hi is an integer,
90+
// 0 <= mid * 2^5 < 32 is an integer,
91+
// -2^(-6) <= lo <= 2^(-6).
92+
// In particular,
93+
// hi + mid = round(x * 2^5) * 2^(-5).
94+
// Then,
95+
// 2^x = 2^(hi + mid + lo) = 2^hi * 2^mid * 2^lo.
96+
// 2^mid is stored in the lookup table of 32 elements.
97+
// 2^lo is computed using a degree-4 minimax polynomial generated by Sollya.
98+
// We perform 2^hi * 2^mid by simply add hi to the exponent field of 2^mid.
99+
100+
// kf = (hi + mid) * 2^5 = round(x * 2^5)
101+
float kf;
102+
int k;
103+
#ifdef LIBC_TARGET_CPU_HAS_NEAREST_INT
104+
kf = fputil::nearest_integer(x * 32.0f);
105+
k = static_cast<int>(kf);
106+
#else
107+
constexpr float HALF[2] = {0.5f, -0.5f};
108+
k = static_cast<int>(fputil::multiply_add(x, 32.0f, HALF[x < 0.0f]));
109+
kf = static_cast<float>(k);
110+
#endif // LIBC_TARGET_CPU_HAS_NEAREST_INT
111+
112+
// dx = lo = x - (hi + mid) = x - kf * 2^(-5)
113+
double dx = fputil::multiply_add(-0x1.0p-5f, kf, x);
114+
115+
// hi = floor(kf * 2^(-4))
116+
// exp_hi = shift hi to the exponent field of double precision.
117+
int64_t exp_hi =
118+
static_cast<int64_t>(static_cast<uint64_t>(k >> ExpBase::MID_BITS)
119+
<< fputil::FPBits<double>::FRACTION_LEN);
120+
// mh = 2^hi * 2^mid
121+
// mh_bits = bit field of mh
122+
int64_t mh_bits = ExpBase::EXP_2_MID[k & ExpBase::MID_MASK] + exp_hi;
123+
double mh = fputil::FPBits<double>(static_cast<uint64_t>(mh_bits)).get_val();
124+
125+
// Degree-4 polynomial approximating (2^x - 1)/x generated by Sollya with:
126+
// > display = hexadecimal;
127+
// > fpminimax((2^x - 1)/x, 4, [|D...|], [-2^-6, 2^-6]);
128+
constexpr double COEFFS[5] = {0x1.62e42fefa39efp-1, 0x1.ebfbdff8131c4p-3,
129+
0x1.c6b08d7061695p-5, 0x1.3b2b1bee74b2ap-7,
130+
0x1.5d88091198529p-10};
131+
double dx_sq = dx * dx;
132+
double c1 = fputil::multiply_add(dx, COEFFS[0], 1.0);
133+
double c2 = fputil::multiply_add(dx, COEFFS[2], COEFFS[1]);
134+
double c3 = fputil::multiply_add(dx, COEFFS[4], COEFFS[3]);
135+
double p = fputil::multiply_add(dx_sq, c3, c2);
136+
// 2^x = 2^(hi + mid + lo)
137+
// = 2^(hi + mid) * 2^lo
138+
// ~ mh * (1 + lo * P(lo))
139+
// = mh + (mh*lo) * P(lo)
140+
double exp2_lo = fputil::multiply_add(p, dx_sq, c1);
141+
return static_cast<float>(fputil::multiply_add(exp2_lo, mh, -1.0));
142+
}
143+
144+
} // namespace LIBC_NAMESPACE

libc/test/src/math/exhaustive/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,21 @@ add_fp_unittest(
142142
-lpthread
143143
)
144144

145+
add_fp_unittest(
146+
exp2m1f_test
147+
NO_RUN_POSTBUILD
148+
NEED_MPFR
149+
SUITE
150+
libc_math_exhaustive_tests
151+
SRCS
152+
exp2m1f_test.cpp
153+
DEPENDS
154+
.exhaustive_test
155+
libc.src.math.exp2m1f
156+
LINK_LIBRARIES
157+
-lpthread
158+
)
159+
145160
add_fp_unittest(
146161
exp10f_test
147162
NO_RUN_POSTBUILD
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//===-- Exhaustive test for exp2m1f ---------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "exhaustive_test.h"
10+
#include "src/math/exp2m1f.h"
11+
#include "utils/MPFRWrapper/MPFRUtils.h"
12+
13+
namespace mpfr = LIBC_NAMESPACE::testing::mpfr;
14+
15+
using LlvmLibcExp2m1fExhaustiveTest =
16+
LlvmLibcUnaryOpExhaustiveMathTest<float, mpfr::Operation::Exp2m1,
17+
LIBC_NAMESPACE::exp2m1f>;
18+
19+
// Range: [0, Inf];
20+
static constexpr uint32_t POS_START = 0x0000'0000U;
21+
static constexpr uint32_t POS_STOP = 0x7f80'0000U;
22+
23+
TEST_F(LlvmLibcExp2m1fExhaustiveTest, PostiveRange) {
24+
test_full_range_all_roundings(POS_START, POS_STOP);
25+
}
26+
27+
// Range: [-Inf, 0];
28+
static constexpr uint32_t NEG_START = 0x8000'0000U;
29+
static constexpr uint32_t NEG_STOP = 0xff80'0000U;
30+
31+
TEST_F(LlvmLibcExp2m1fExhaustiveTest, NegativeRange) {
32+
test_full_range_all_roundings(NEG_START, NEG_STOP);
33+
}

libc/test/src/math/smoke/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,18 @@ add_fp_unittest(
773773
libc.src.__support.FPUtil.fp_bits
774774
)
775775

776+
add_fp_unittest(
777+
exp2m1f_test
778+
SUITE
779+
libc-math-smoke-tests
780+
SRCS
781+
exp2m1f_test.cpp
782+
DEPENDS
783+
libc.src.__support.FPUtil.fp_bits
784+
libc.src.errno.errno
785+
libc.src.math.exp2m1f
786+
)
787+
776788
add_fp_unittest(
777789
exp10f_test
778790
SUITE
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//===-- Unittests for exp2m1f ---------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "src/__support/FPUtil/FPBits.h"
10+
#include "src/errno/libc_errno.h"
11+
#include "src/math/exp2m1f.h"
12+
#include "test/UnitTest/FPMatcher.h"
13+
#include "test/UnitTest/Test.h"
14+
15+
using LlvmLibcExp2m1fTest = LIBC_NAMESPACE::testing::FPTest<float>;
16+
17+
TEST_F(LlvmLibcExp2m1fTest, SpecialNumbers) {
18+
LIBC_NAMESPACE::libc_errno = 0;
19+
20+
EXPECT_FP_EQ_ALL_ROUNDING(aNaN, LIBC_NAMESPACE::exp2m1f(aNaN));
21+
EXPECT_MATH_ERRNO(0);
22+
23+
EXPECT_FP_EQ_ALL_ROUNDING(inf, LIBC_NAMESPACE::exp2m1f(inf));
24+
EXPECT_MATH_ERRNO(0);
25+
26+
EXPECT_FP_EQ_ALL_ROUNDING(-1.0f, LIBC_NAMESPACE::exp2m1f(neg_inf));
27+
EXPECT_MATH_ERRNO(0);
28+
29+
EXPECT_FP_EQ_ALL_ROUNDING(0.0f, LIBC_NAMESPACE::exp2m1f(0.0f));
30+
EXPECT_MATH_ERRNO(0);
31+
32+
EXPECT_FP_EQ_ALL_ROUNDING(-0.0f, LIBC_NAMESPACE::exp2m1f(-0.0f));
33+
EXPECT_MATH_ERRNO(0);
34+
35+
EXPECT_FP_EQ_ALL_ROUNDING(1.0f, LIBC_NAMESPACE::exp2m1f(1.0f));
36+
EXPECT_FP_EQ_ALL_ROUNDING(-0.5f, LIBC_NAMESPACE::exp2m1f(-1.0f));
37+
EXPECT_FP_EQ_ALL_ROUNDING(3.0f, LIBC_NAMESPACE::exp2m1f(2.0f));
38+
EXPECT_FP_EQ_ALL_ROUNDING(-0.75f, LIBC_NAMESPACE::exp2m1f(-2.0f));
39+
}
40+
41+
TEST_F(LlvmLibcExp2m1fTest, Overflow) {
42+
LIBC_NAMESPACE::libc_errno = 0;
43+
EXPECT_FP_EQ_WITH_EXCEPTION(
44+
inf, LIBC_NAMESPACE::exp2m1f(FPBits(0x7f7f'ffffU).get_val()),
45+
FE_OVERFLOW);
46+
EXPECT_MATH_ERRNO(ERANGE);
47+
48+
EXPECT_FP_EQ_WITH_EXCEPTION(
49+
inf, LIBC_NAMESPACE::exp2m1f(FPBits(0x4300'0000U).get_val()),
50+
FE_OVERFLOW);
51+
EXPECT_MATH_ERRNO(ERANGE);
52+
53+
EXPECT_FP_EQ_WITH_EXCEPTION(
54+
inf, LIBC_NAMESPACE::exp2m1f(FPBits(0x4300'0001U).get_val()),
55+
FE_OVERFLOW);
56+
EXPECT_MATH_ERRNO(ERANGE);
57+
}

libc/utils/MPFRWrapper/MPFRUtils.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@ class MPFRNumber {
229229
return result;
230230
}
231231

232+
MPFRNumber exp2m1() const {
233+
MPFRNumber result(*this);
234+
mpfr_exp2m1(result.value, value, mpfr_rounding);
235+
return result;
236+
}
237+
232238
MPFRNumber exp10() const {
233239
MPFRNumber result(*this);
234240
mpfr_exp10(result.value, value, mpfr_rounding);
@@ -570,6 +576,8 @@ unary_operation(Operation op, InputType input, unsigned int precision,
570576
return mpfrInput.exp();
571577
case Operation::Exp2:
572578
return mpfrInput.exp2();
579+
case Operation::Exp2m1:
580+
return mpfrInput.exp2m1();
573581
case Operation::Exp10:
574582
return mpfrInput.exp10();
575583
case Operation::Expm1:

libc/utils/MPFRWrapper/MPFRUtils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ enum class Operation : int {
3737
Erf,
3838
Exp,
3939
Exp2,
40+
Exp2m1,
4041
Exp10,
4142
Expm1,
4243
Floor,

0 commit comments

Comments
 (0)