66 changes: 66 additions & 0 deletions libc/test/src/math/exp2m1f_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//===-- Unittests for exp2m1f ---------------------------------------------===//
//
// 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 "include/llvm-libc-macros/math-macros.h"
#include "src/__support/CPP/array.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/errno/libc_errno.h"
#include "src/math/exp2m1f.h"
#include "test/UnitTest/FPMatcher.h"
#include "test/UnitTest/Test.h"
#include "utils/MPFRWrapper/MPFRUtils.h"

#include <stdint.h>

using LlvmLibcExp2m1fTest = LIBC_NAMESPACE::testing::FPTest<float>;

namespace mpfr = LIBC_NAMESPACE::testing::mpfr;

TEST_F(LlvmLibcExp2m1fTest, TrickyInputs) {
constexpr LIBC_NAMESPACE::cpp::array<float, 10> INPUTS = {
// EXP2M1F_EXCEPTS_LO
0x1.36dc8ep-36,
0x1.224936p-19,
0x1.d16d2p-20,
0x1.17949ep-14,
-0x1.9c3e1ep-38,
-0x1.4d89b4p-32,
-0x1.a6eac4p-10,
-0x1.e7526ep-6,
// EXP2M1F_EXCEPTS_HI
0x1.16a972p-1,
-0x1.9f12acp-5,
};

for (float x : INPUTS) {
LIBC_NAMESPACE::libc_errno = 0;
EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Exp2m1, x,
LIBC_NAMESPACE::exp2m1f(x), 0.5);
}
}

TEST_F(LlvmLibcExp2m1fTest, 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::exp2m1f(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::Exp2m1, x,
LIBC_NAMESPACE::exp2m1f(x), 0.5);
}
}
11 changes: 11 additions & 0 deletions libc/test/src/math/smoke/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,17 @@ add_fp_unittest(
libc.src.__support.FPUtil.fp_bits
)

add_fp_unittest(
exp2m1f_test
SUITE
libc-math-smoke-tests
SRCS
exp2m1f_test.cpp
DEPENDS
libc.src.errno.errno
libc.src.math.exp2m1f
)

add_fp_unittest(
exp10f_test
SUITE
Expand Down
63 changes: 63 additions & 0 deletions libc/test/src/math/smoke/exp2m1f_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//===-- Unittests for exp2m1f ---------------------------------------------===//
//
// 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/errno/libc_errno.h"
#include "src/math/exp2m1f.h"
#include "test/UnitTest/FPMatcher.h"
#include "test/UnitTest/Test.h"

using LlvmLibcExp2m1fTest = LIBC_NAMESPACE::testing::FPTest<float>;
using LIBC_NAMESPACE::fputil::testing::ForceRoundingMode;
using LIBC_NAMESPACE::fputil::testing::RoundingMode;

TEST_F(LlvmLibcExp2m1fTest, SpecialNumbers) {
LIBC_NAMESPACE::libc_errno = 0;

EXPECT_FP_EQ_ALL_ROUNDING(aNaN, LIBC_NAMESPACE::exp2m1f(aNaN));
EXPECT_FP_EQ_ALL_ROUNDING(inf, LIBC_NAMESPACE::exp2m1f(inf));
EXPECT_FP_EQ_ALL_ROUNDING(-1.0f, LIBC_NAMESPACE::exp2m1f(neg_inf));
EXPECT_FP_EQ_ALL_ROUNDING(0.0f, LIBC_NAMESPACE::exp2m1f(0.0f));
EXPECT_FP_EQ_ALL_ROUNDING(-0.0f, LIBC_NAMESPACE::exp2m1f(-0.0f));

EXPECT_FP_EQ_ALL_ROUNDING(1.0f, LIBC_NAMESPACE::exp2m1f(1.0f));
EXPECT_FP_EQ_ALL_ROUNDING(-0.5f, LIBC_NAMESPACE::exp2m1f(-1.0f));
EXPECT_FP_EQ_ALL_ROUNDING(3.0f, LIBC_NAMESPACE::exp2m1f(2.0f));
EXPECT_FP_EQ_ALL_ROUNDING(-0.75f, LIBC_NAMESPACE::exp2m1f(-2.0f));
}

TEST_F(LlvmLibcExp2m1fTest, Overflow) {
LIBC_NAMESPACE::libc_errno = 0;

EXPECT_FP_EQ_WITH_EXCEPTION(inf, LIBC_NAMESPACE::exp2m1f(0x1.fffffep+127),
FE_OVERFLOW);
EXPECT_MATH_ERRNO(ERANGE);

EXPECT_FP_EQ_WITH_EXCEPTION(inf, LIBC_NAMESPACE::exp2m1f(128.0f),
FE_OVERFLOW);
EXPECT_MATH_ERRNO(ERANGE);

EXPECT_FP_EQ_WITH_EXCEPTION(inf, LIBC_NAMESPACE::exp2m1f(0x1.000002p+7),
FE_OVERFLOW);
EXPECT_MATH_ERRNO(ERANGE);
}

TEST_F(LlvmLibcExp2m1fTest, Underflow) {
LIBC_NAMESPACE::libc_errno = 0;

EXPECT_FP_EQ_WITH_EXCEPTION(-1.0f, LIBC_NAMESPACE::exp2m1f(-0x1.fffffep+127),
FE_UNDERFLOW);
EXPECT_MATH_ERRNO(ERANGE);

EXPECT_FP_EQ_WITH_EXCEPTION(-1.0f, LIBC_NAMESPACE::exp2m1f(-25.0f),
FE_UNDERFLOW);
EXPECT_MATH_ERRNO(ERANGE);

EXPECT_FP_EQ_WITH_EXCEPTION(-1.0f, LIBC_NAMESPACE::exp2m1f(-0x1.900002p4),
FE_UNDERFLOW);
EXPECT_MATH_ERRNO(ERANGE);
}
50 changes: 46 additions & 4 deletions libc/utils/MPFRWrapper/MPFRUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ class MPFRNumber {
// precision.
template <typename XType,
cpp::enable_if_t<cpp::is_same_v<float, XType>, int> = 0>
explicit MPFRNumber(XType x, int precision = ExtraPrecision<XType>::VALUE,
explicit MPFRNumber(XType x,
unsigned int precision = ExtraPrecision<XType>::VALUE,
RoundingMode rounding = RoundingMode::Nearest)
: mpfr_precision(precision),
mpfr_rounding(get_mpfr_rounding_mode(rounding)) {
Expand All @@ -99,7 +100,8 @@ class MPFRNumber {

template <typename XType,
cpp::enable_if_t<cpp::is_same_v<double, XType>, int> = 0>
explicit MPFRNumber(XType x, int precision = ExtraPrecision<XType>::VALUE,
explicit MPFRNumber(XType x,
unsigned int precision = ExtraPrecision<XType>::VALUE,
RoundingMode rounding = RoundingMode::Nearest)
: mpfr_precision(precision),
mpfr_rounding(get_mpfr_rounding_mode(rounding)) {
Expand All @@ -109,7 +111,8 @@ class MPFRNumber {

template <typename XType,
cpp::enable_if_t<cpp::is_same_v<long double, XType>, int> = 0>
explicit MPFRNumber(XType x, int precision = ExtraPrecision<XType>::VALUE,
explicit MPFRNumber(XType x,
unsigned int precision = ExtraPrecision<XType>::VALUE,
RoundingMode rounding = RoundingMode::Nearest)
: mpfr_precision(precision),
mpfr_rounding(get_mpfr_rounding_mode(rounding)) {
Expand All @@ -119,7 +122,8 @@ class MPFRNumber {

template <typename XType,
cpp::enable_if_t<cpp::is_integral_v<XType>, int> = 0>
explicit MPFRNumber(XType x, int precision = ExtraPrecision<float>::VALUE,
explicit MPFRNumber(XType x,
unsigned int precision = ExtraPrecision<float>::VALUE,
RoundingMode rounding = RoundingMode::Nearest)
: mpfr_precision(precision),
mpfr_rounding(get_mpfr_rounding_mode(rounding)) {
Expand All @@ -134,6 +138,12 @@ class MPFRNumber {
mpfr_set(value, other.value, mpfr_rounding);
}

MPFRNumber(const MPFRNumber &other, unsigned int precision)
: mpfr_precision(precision), mpfr_rounding(other.mpfr_rounding) {
mpfr_init2(value, mpfr_precision);
mpfr_set(value, other.value, mpfr_rounding);
}

~MPFRNumber() { mpfr_clear(value); }

MPFRNumber &operator=(const MPFRNumber &rhs) {
Expand Down Expand Up @@ -229,6 +239,36 @@ class MPFRNumber {
return result;
}

MPFRNumber exp2m1() const {
// TODO: Only use mpfr_exp2m1 once CI and buildbots get MPFR >= 4.2.0.
#if MPFR_VERSION_MAJOR > 4 || \
(MPFR_VERSION_MAJOR == 4 && MPFR_VERSION_MINOR >= 2)
MPFRNumber result(*this);
mpfr_exp2m1(result.value, value, mpfr_rounding);
return result;
#else
unsigned int prec = mpfr_precision * 3;
MPFRNumber result(*this, prec);

float f = mpfr_get_flt(abs().value, mpfr_rounding);
if (f > 0.5f && f < 0x1.0p30f) {
mpfr_exp2(result.value, value, mpfr_rounding);
mpfr_sub_ui(result.value, result.value, 1, mpfr_rounding);
return result;
}

MPFRNumber ln2(2.0f, prec);
// log(2)
mpfr_log(ln2.value, ln2.value, mpfr_rounding);
// x * log(2)
mpfr_mul(result.value, value, ln2.value, mpfr_rounding);
// e^(x * log(2)) - 1
int ex = mpfr_expm1(result.value, result.value, mpfr_rounding);
mpfr_subnormalize(result.value, ex, mpfr_rounding);
return result;
#endif
}

MPFRNumber exp10() const {
MPFRNumber result(*this);
mpfr_exp10(result.value, value, mpfr_rounding);
Expand Down Expand Up @@ -570,6 +610,8 @@ unary_operation(Operation op, InputType input, unsigned int precision,
return mpfrInput.exp();
case Operation::Exp2:
return mpfrInput.exp2();
case Operation::Exp2m1:
return mpfrInput.exp2m1();
case Operation::Exp10:
return mpfrInput.exp10();
case Operation::Expm1:
Expand Down
1 change: 1 addition & 0 deletions libc/utils/MPFRWrapper/MPFRUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ enum class Operation : int {
Erf,
Exp,
Exp2,
Exp2m1,
Exp10,
Expm1,
Floor,
Expand Down