Skip to content

Commit a8c5975

Browse files
authored
[libc][math][c23] Add exp2m1f C23 math function (#86996)
Fixes #86502. cc @lntue
1 parent 5ad320a commit a8c5975

File tree

15 files changed

+478
-5
lines changed

15 files changed

+478
-5
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/docs/math/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ Higher Math Functions
270270
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
271271
| exp2 | |check| | |check| | | | | 7.12.6.4 | F.10.3.4 |
272272
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
273-
| exp2m1 | | | | | | 7.12.6.5 | F.10.3.5 |
273+
| exp2m1 | |check| | | | | | 7.12.6.5 | F.10.3.5 |
274274
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
275275
| expm1 | |check| | |check| | | | | 7.12.6.6 | F.10.3.6 |
276276
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+

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: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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/except_value_utils.h"
14+
#include "src/__support/FPUtil/multiply_add.h"
15+
#include "src/__support/FPUtil/rounding_mode.h"
16+
#include "src/__support/common.h"
17+
#include "src/__support/macros/optimization.h"
18+
#include "src/__support/macros/properties/cpu_features.h"
19+
#include "src/errno/libc_errno.h"
20+
21+
#include "explogxf.h"
22+
23+
namespace LIBC_NAMESPACE {
24+
25+
static constexpr size_t N_EXCEPTS_LO = 8;
26+
27+
static constexpr fputil::ExceptValues<float, N_EXCEPTS_LO> EXP2M1F_EXCEPTS_LO =
28+
{{
29+
// (input, RZ output, RU offset, RD offset, RN offset)
30+
// x = 0x1.36dc8ep-36, exp2m1f(x) = 0x1.aef212p-37 (RZ)
31+
{0x2d9b'6e47U, 0x2d57'7909U, 1U, 0U, 0U},
32+
// x = 0x1.224936p-19, exp2m1f(x) = 0x1.926c0ep-20 (RZ)
33+
{0x3611'249bU, 0x35c9'3607U, 1U, 0U, 1U},
34+
// x = 0x1.d16d2p-20, exp2m1f(x) = 0x1.429becp-20 (RZ)
35+
{0x35e8'b690U, 0x35a1'4df6U, 1U, 0U, 1U},
36+
// x = 0x1.17949ep-14, exp2m1f(x) = 0x1.8397p-15 (RZ)
37+
{0x388b'ca4fU, 0x3841'cb80U, 1U, 0U, 1U},
38+
// x = -0x1.9c3e1ep-38, exp2m1f(x) = -0x1.1dbeacp-38 (RZ)
39+
{0xacce'1f0fU, 0xac8e'df56U, 0U, 1U, 0U},
40+
// x = -0x1.4d89b4p-32, exp2m1f(x) = -0x1.ce61b6p-33 (RZ)
41+
{0xafa6'c4daU, 0xaf67'30dbU, 0U, 1U, 1U},
42+
// x = -0x1.a6eac4p-10, exp2m1f(x) = -0x1.24fadap-10 (RZ)
43+
{0xbad3'7562U, 0xba92'7d6dU, 0U, 1U, 1U},
44+
// x = -0x1.e7526ep-6, exp2m1f(x) = -0x1.4e53dep-6 (RZ)
45+
{0xbcf3'a937U, 0xbca7'29efU, 0U, 1U, 1U},
46+
}};
47+
48+
static constexpr size_t N_EXCEPTS_HI = 3;
49+
50+
static constexpr fputil::ExceptValues<float, N_EXCEPTS_HI> EXP2M1F_EXCEPTS_HI =
51+
{{
52+
// (input, RZ output, RU offset, RD offset, RN offset)
53+
// x = 0x1.16a972p-1, exp2m1f(x) = 0x1.d545b2p-2 (RZ)
54+
{0x3f0b'54b9U, 0x3eea'a2d9U, 1U, 0U, 0U},
55+
// x = -0x1.9f12acp-5, exp2m1f(x) = -0x1.1ab68cp-5 (RZ)
56+
{0xbd4f'8956U, 0xbd0d'5b46U, 0U, 1U, 0U},
57+
// x = -0x1.de7b9cp-5, exp2m1f(x) = -0x1.4508f4p-5 (RZ)
58+
{0xbd6f'3dceU, 0xbd22'847aU, 0U, 1U, 1U},
59+
}};
60+
61+
LLVM_LIBC_FUNCTION(float, exp2m1f, (float x)) {
62+
using FPBits = fputil::FPBits<float>;
63+
FPBits xbits(x);
64+
65+
uint32_t x_u = xbits.uintval();
66+
uint32_t x_abs = x_u & 0x7fff'ffffU;
67+
68+
// When |x| >= 128, or x is nan, or |x| <= 2^-5
69+
if (LIBC_UNLIKELY(x_abs >= 0x4300'0000U || x_abs <= 0x3d00'0000U)) {
70+
// |x| <= 2^-5
71+
if (x_abs <= 0x3d00'0000U) {
72+
if (auto r = EXP2M1F_EXCEPTS_LO.lookup(x_u); LIBC_UNLIKELY(r.has_value()))
73+
return r.value();
74+
75+
// Minimax polynomial generated by Sollya with:
76+
// > display = hexadecimal;
77+
// > fpminimax((2^x - 1)/x, 5, [|D...|], [-2^-5, 2^-5]);
78+
constexpr double COEFFS[] = {
79+
0x1.62e42fefa39f3p-1, 0x1.ebfbdff82c57bp-3, 0x1.c6b08d6f2d7aap-5,
80+
0x1.3b2ab6fc92f5dp-7, 0x1.5d897cfe27125p-10, 0x1.43090e61e6af1p-13};
81+
double xd = x;
82+
double xsq = xd * xd;
83+
double c0 = fputil::multiply_add(xd, COEFFS[1], COEFFS[0]);
84+
double c1 = fputil::multiply_add(xd, COEFFS[3], COEFFS[2]);
85+
double c2 = fputil::multiply_add(xd, COEFFS[5], COEFFS[4]);
86+
double p = fputil::polyeval(xsq, c0, c1, c2);
87+
return static_cast<float>(p * xd);
88+
}
89+
90+
// x >= 128, or x is nan
91+
if (xbits.is_pos()) {
92+
if (xbits.is_finite()) {
93+
int rounding = fputil::quick_get_round();
94+
if (rounding == FE_DOWNWARD || rounding == FE_TOWARDZERO)
95+
return FPBits::max_normal().get_val();
96+
97+
fputil::set_errno_if_required(ERANGE);
98+
fputil::raise_except_if_required(FE_OVERFLOW);
99+
}
100+
101+
// x >= 128 and 2^x - 1 rounds to +inf, or x is +inf or nan
102+
return x + FPBits::inf().get_val();
103+
}
104+
}
105+
106+
if (LIBC_UNLIKELY(x <= -25.0f)) {
107+
// 2^(-inf) - 1 = -1
108+
if (xbits.is_inf())
109+
return -1.0f;
110+
// 2^nan - 1 = nan
111+
if (xbits.is_nan())
112+
return x;
113+
114+
int rounding = fputil::quick_get_round();
115+
if (rounding == FE_UPWARD || rounding == FE_TOWARDZERO)
116+
return -0x1.ffff'fep-1f; // -1.0f + 0x1.0p-24f
117+
118+
fputil::set_errno_if_required(ERANGE);
119+
fputil::raise_except_if_required(FE_UNDERFLOW);
120+
return -1.0f;
121+
}
122+
123+
if (auto r = EXP2M1F_EXCEPTS_HI.lookup(x_u); LIBC_UNLIKELY(r.has_value()))
124+
return r.value();
125+
126+
// For -25 < x < 128, to compute 2^x, we perform the following range
127+
// reduction: find hi, mid, lo such that:
128+
// x = hi + mid + lo, in which:
129+
// hi is an integer,
130+
// 0 <= mid * 2^5 < 32 is an integer,
131+
// -2^(-6) <= lo <= 2^(-6).
132+
// In particular,
133+
// hi + mid = round(x * 2^5) * 2^(-5).
134+
// Then,
135+
// 2^x = 2^(hi + mid + lo) = 2^hi * 2^mid * 2^lo.
136+
// 2^mid is stored in the lookup table of 32 elements.
137+
// 2^lo is computed using a degree-4 minimax polynomial generated by Sollya.
138+
// We perform 2^hi * 2^mid by simply add hi to the exponent field of 2^mid.
139+
140+
// kf = (hi + mid) * 2^5 = round(x * 2^5)
141+
float kf;
142+
int k;
143+
#ifdef LIBC_TARGET_CPU_HAS_NEAREST_INT
144+
kf = fputil::nearest_integer(x * 32.0f);
145+
k = static_cast<int>(kf);
146+
#else
147+
constexpr float HALF[2] = {0.5f, -0.5f};
148+
k = static_cast<int>(fputil::multiply_add(x, 32.0f, HALF[x < 0.0f]));
149+
kf = static_cast<float>(k);
150+
#endif // LIBC_TARGET_CPU_HAS_NEAREST_INT
151+
152+
// lo = x - (hi + mid) = x - kf * 2^(-5)
153+
double lo = fputil::multiply_add(-0x1.0p-5f, kf, x);
154+
155+
// hi = floor(kf * 2^(-4))
156+
// exp2_hi = shift hi to the exponent field of double precision.
157+
int64_t exp2_hi =
158+
static_cast<int64_t>(static_cast<uint64_t>(k >> ExpBase::MID_BITS)
159+
<< fputil::FPBits<double>::FRACTION_LEN);
160+
// mh = 2^hi * 2^mid
161+
// mh_bits = bit field of mh
162+
int64_t mh_bits = ExpBase::EXP_2_MID[k & ExpBase::MID_MASK] + exp2_hi;
163+
double mh = fputil::FPBits<double>(static_cast<uint64_t>(mh_bits)).get_val();
164+
165+
// Degree-4 polynomial approximating (2^x - 1)/x generated by Sollya with:
166+
// > display = hexadecimal;
167+
// > fpminimax((2^x - 1)/x, 4, [|D...|], [-2^-6, 2^-6]);
168+
constexpr double COEFFS[5] = {0x1.62e42fefa39efp-1, 0x1.ebfbdff8131c4p-3,
169+
0x1.c6b08d7061695p-5, 0x1.3b2b1bee74b2ap-7,
170+
0x1.5d88091198529p-10};
171+
double lo_sq = lo * lo;
172+
double c1 = fputil::multiply_add(lo, COEFFS[0], 1.0);
173+
double c2 = fputil::multiply_add(lo, COEFFS[2], COEFFS[1]);
174+
double c3 = fputil::multiply_add(lo, COEFFS[4], COEFFS[3]);
175+
double exp2_lo = fputil::polyeval(lo_sq, c1, c2, c3);
176+
// 2^x - 1 = 2^(hi + mid + lo) - 1
177+
// = 2^(hi + mid) * 2^lo - 1
178+
// ~ mh * (1 + lo * P(lo)) - 1
179+
// = mh * exp2_lo - 1
180+
return static_cast<float>(fputil::multiply_add(exp2_lo, mh, -1.0));
181+
}
182+
183+
} // namespace LIBC_NAMESPACE

libc/test/src/math/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,21 @@ add_fp_unittest(
637637
libc.src.__support.FPUtil.fp_bits
638638
)
639639

640+
add_fp_unittest(
641+
exp2m1f_test
642+
NEED_MPFR
643+
SUITE
644+
libc-math-unittests
645+
SRCS
646+
exp2m1f_test.cpp
647+
DEPENDS
648+
libc.include.llvm-libc-macros.math_macros
649+
libc.src.errno.errno
650+
libc.src.math.exp2m1f
651+
libc.src.__support.CPP.array
652+
libc.src.__support.FPUtil.fp_bits
653+
)
654+
640655
add_fp_unittest(
641656
exp10f_test
642657
NEED_MPFR

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+
}

0 commit comments

Comments
 (0)