65 changes: 65 additions & 0 deletions libc/src/math/generic/expxf16.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,76 @@
#define LLVM_LIBC_SRC_MATH_GENERIC_EXPXF16_H

#include "src/__support/CPP/array.h"
#include "src/__support/FPUtil/PolyEval.h"
#include "src/__support/FPUtil/multiply_add.h"
#include "src/__support/FPUtil/nearest_integer.h"
#include "src/__support/macros/config.h"
#include <stdint.h>

namespace LIBC_NAMESPACE_DECL {

// Generated by Sollya with the following commands:
// > display = hexadecimal;
// > for i from -18 to 12 do print(round(exp(i), SG, RN));
static constexpr cpp::array<float, 31> EXP_HI = {
0x1.05a628p-26f, 0x1.639e32p-25f, 0x1.e355bcp-24f, 0x1.4875cap-22f,
0x1.be6c7p-21f, 0x1.2f6054p-19f, 0x1.9c54c4p-18f, 0x1.183542p-16f,
0x1.7cd79cp-15f, 0x1.02cf22p-13f, 0x1.5fc21p-12f, 0x1.de16bap-11f,
0x1.44e52p-9f, 0x1.b993fep-8f, 0x1.2c155cp-6f, 0x1.97db0cp-5f,
0x1.152aaap-3f, 0x1.78b564p-2f, 0x1p+0f, 0x1.5bf0a8p+1f,
0x1.d8e64cp+2f, 0x1.415e5cp+4f, 0x1.b4c902p+5f, 0x1.28d38ap+7f,
0x1.936dc6p+8f, 0x1.122886p+10f, 0x1.749ea8p+11f, 0x1.fa7158p+12f,
0x1.5829dcp+14f, 0x1.d3c448p+15f, 0x1.3de166p+17f,
};

// Generated by Sollya with the following commands:
// > display = hexadecimal;
// > for i from 0 to 7 do print(round(exp(i * 2^-3), SG, RN));
static constexpr cpp::array<float, 8> EXP_MID = {
0x1p+0f, 0x1.221604p+0f, 0x1.48b5e4p+0f, 0x1.747a52p+0f,
0x1.a61298p+0f, 0x1.de455ep+0f, 0x1.0ef9dcp+1f, 0x1.330e58p+1f,
};

struct ExpRangeReduction {
float exp_hi_mid;
float exp_lo;
};

ExpRangeReduction exp_range_reduction(float16 x) {
// For -18 < x < 12, to compute exp(x), we perform the following range
// reduction: find hi, mid, lo, such that:
// x = hi + mid + lo, in which
// hi is an integer,
// mid * 2^3 is an integer,
// -2^(-4) <= lo < 2^(-4).
// In particular,
// hi + mid = round(x * 2^3) * 2^(-3).
// Then,
// exp(x) = exp(hi + mid + lo) = exp(hi) * exp(mid) * exp(lo).
// We store exp(hi) and exp(mid) in the lookup tables EXP_HI and EXP_MID
// respectively. exp(lo) is computed using a degree-3 minimax polynomial
// generated by Sollya.

float xf = x;
float kf = fputil::nearest_integer(xf * 0x1.0p+3f);
int x_hi_mid = static_cast<int>(kf);
int x_hi = x_hi_mid >> 3;
int x_mid = x_hi_mid & 0x7;
// lo = x - (hi + mid) = round(x * 2^3) * (-2^(-3)) + x
float lo = fputil::multiply_add(kf, -0x1.0p-3f, xf);

float exp_hi = EXP_HI[x_hi + 18];
float exp_mid = EXP_MID[x_mid];
// Degree-3 minimax polynomial generated by Sollya with the following
// commands:
// > display = hexadecimal;
// > P = fpminimax(expm1(x)/x, 2, [|SG...|], [-2^-4, 2^-4]);
// > 1 + x * P;
float exp_lo =
fputil::polyeval(lo, 0x1p+0f, 0x1p+0f, 0x1.001p-1f, 0x1.555ddep-3f);
return {exp_hi * exp_mid, exp_lo};
}

// Generated by Sollya with the following commands:
// > display = hexadecimal;
// > for i from 0 to 7 do printsingle(round(2^(i * 2^-3), SG, RN));
Expand Down
32 changes: 32 additions & 0 deletions libc/test/UnitTest/FPMatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,4 +234,36 @@ template <typename T> struct FPTest : public Test {
#define EXPECT_FP_EQ_ROUNDING_TOWARD_ZERO(expected, actual) \
EXPECT_FP_EQ_ROUNDING_MODE((expected), (actual), RoundingMode::TowardZero)

#define EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_MODE( \
expected, actual, expected_except, rounding_mode) \
do { \
using namespace LIBC_NAMESPACE::fputil::testing; \
ForceRoundingMode __r((rounding_mode)); \
if (__r.success) { \
LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT); \
EXPECT_FP_EQ((expected), (actual)); \
EXPECT_FP_EXCEPTION(expected_except); \
} \
} while (0)

#define EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_NEAREST(expected, actual, \
expected_except) \
EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_MODE( \
(expected), (actual), (expected_except), RoundingMode::Nearest)

#define EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_UPWARD(expected, actual, \
expected_except) \
EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_MODE( \
(expected), (actual), (expected_except), RoundingMode::Upward)

#define EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_DOWNWARD(expected, actual, \
expected_except) \
EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_MODE( \
(expected), (actual), (expected_except), RoundingMode::Downward)

#define EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_TOWARD_ZERO(expected, actual, \
expected_except) \
EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_MODE( \
(expected), (actual), (expected_except), RoundingMode::TowardZero)

#endif // LLVM_LIBC_TEST_UNITTEST_FPMATCHER_H
31 changes: 21 additions & 10 deletions libc/test/src/math/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1676,6 +1676,19 @@ add_fp_unittest(
libc.src.__support.FPUtil.fp_bits
)

add_fp_unittest(
expm1_test
NEED_MPFR
SUITE
libc-math-unittests
SRCS
expm1_test.cpp
DEPENDS
libc.src.errno.errno
libc.src.math.expm1
libc.src.__support.FPUtil.fp_bits
)

add_fp_unittest(
expm1f_test
NEED_MPFR
Expand All @@ -1690,16 +1703,14 @@ add_fp_unittest(
)

add_fp_unittest(
expm1_test
NEED_MPFR
SUITE
libc-math-unittests
SRCS
expm1_test.cpp
DEPENDS
libc.src.errno.errno
libc.src.math.expm1
libc.src.__support.FPUtil.fp_bits
expm1f16_test
NEED_MPFR
SUITE
libc-math-unittests
SRCS
expm1f16_test.cpp
DEPENDS
libc.src.math.expm1f16
)

add_fp_unittest(
Expand Down
40 changes: 40 additions & 0 deletions libc/test/src/math/expm1f16_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//===-- Exhaustive test for expm1f16 --------------------------------------===//
//
// 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/expm1f16.h"
#include "test/UnitTest/FPMatcher.h"
#include "test/UnitTest/Test.h"
#include "utils/MPFRWrapper/MPFRUtils.h"

using LlvmLibcExpm1f16Test = LIBC_NAMESPACE::testing::FPTest<float16>;

namespace mpfr = LIBC_NAMESPACE::testing::mpfr;

// Range: [0, Inf];
static constexpr uint16_t POS_START = 0x0000U;
static constexpr uint16_t POS_STOP = 0x7c00U;

// Range: [-Inf, 0];
static constexpr uint16_t NEG_START = 0x8000U;
static constexpr uint16_t NEG_STOP = 0xfc00U;

TEST_F(LlvmLibcExpm1f16Test, PositiveRange) {
for (uint16_t v = POS_START; v <= POS_STOP; ++v) {
float16 x = FPBits(v).get_val();
EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Expm1, x,
LIBC_NAMESPACE::expm1f16(x), 0.5);
}
}

TEST_F(LlvmLibcExpm1f16Test, NegativeRange) {
for (uint16_t v = NEG_START; v <= NEG_STOP; ++v) {
float16 x = FPBits(v).get_val();
EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Expm1, x,
LIBC_NAMESPACE::expm1f16(x), 0.5);
}
}
21 changes: 17 additions & 4 deletions libc/test/src/math/smoke/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3324,6 +3324,18 @@ add_fp_unittest(
libc.src.math.fma
)

add_fp_unittest(
expm1_test
SUITE
libc-math-smoke-tests
SRCS
expm1_test.cpp
DEPENDS
libc.src.errno.errno
libc.src.math.expm1
libc.src.__support.FPUtil.fp_bits
)

add_fp_unittest(
expm1f_test
SUITE
Expand All @@ -3337,15 +3349,16 @@ add_fp_unittest(
)

add_fp_unittest(
expm1_test
expm1f16_test
SUITE
libc-math-smoke-tests
SRCS
expm1_test.cpp
expm1f16_test.cpp
DEPENDS
libc.hdr.errno_macros
libc.hdr.fenv_macros
libc.src.errno.errno
libc.src.math.expm1
libc.src.__support.FPUtil.fp_bits
libc.src.math.expm1f16
)

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

using LlvmLibcExpm1f16Test = LIBC_NAMESPACE::testing::FPTest<float16>;

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

EXPECT_FP_EQ_ALL_ROUNDING(aNaN, LIBC_NAMESPACE::expm1f16(aNaN));
EXPECT_MATH_ERRNO(0);

EXPECT_FP_EQ_WITH_EXCEPTION(aNaN, LIBC_NAMESPACE::expm1f16(sNaN), FE_INVALID);
EXPECT_MATH_ERRNO(0);

EXPECT_FP_EQ_ALL_ROUNDING(inf, LIBC_NAMESPACE::expm1f16(inf));
EXPECT_MATH_ERRNO(0);

EXPECT_FP_EQ_ALL_ROUNDING(static_cast<float16>(-1.0),
LIBC_NAMESPACE::expm1f16(neg_inf));
EXPECT_MATH_ERRNO(0);

EXPECT_FP_EQ_ALL_ROUNDING(zero, LIBC_NAMESPACE::expm1f16(zero));
EXPECT_MATH_ERRNO(0);

EXPECT_FP_EQ_ALL_ROUNDING(neg_zero, LIBC_NAMESPACE::expm1f16(neg_zero));
EXPECT_MATH_ERRNO(0);
}

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

EXPECT_FP_EQ_WITH_EXCEPTION(inf, LIBC_NAMESPACE::expm1f16(max_normal),
FE_OVERFLOW | FE_INEXACT);
EXPECT_MATH_ERRNO(ERANGE);

// round(16 * log(2), HP, RN);
float16 x = static_cast<float16>(0x1.63p+3);

EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_NEAREST(inf, LIBC_NAMESPACE::expm1f16(x),
FE_OVERFLOW | FE_INEXACT);
EXPECT_MATH_ERRNO(ERANGE);

EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_UPWARD(inf, LIBC_NAMESPACE::expm1f16(x),
FE_OVERFLOW | FE_INEXACT);
EXPECT_MATH_ERRNO(ERANGE);

EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_DOWNWARD(
max_normal, LIBC_NAMESPACE::expm1f16(x), FE_INEXACT);
EXPECT_MATH_ERRNO(0);

EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_TOWARD_ZERO(
max_normal, LIBC_NAMESPACE::expm1f16(x), FE_INEXACT);
EXPECT_MATH_ERRNO(0);
}

TEST_F(LlvmLibcExpm1f16Test, ResultNearNegOne) {
LIBC_NAMESPACE::libc_errno = 0;

EXPECT_FP_EQ_WITH_EXCEPTION(static_cast<float16>(-1.0),
LIBC_NAMESPACE::expm1f16(neg_max_normal),
FE_INEXACT);

// round(-11 * log(2), HP, RN);
float16 x = static_cast<float16>(-0x1.e8p+2);

EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_NEAREST(
static_cast<float16>(-0x1.ffcp-1), LIBC_NAMESPACE::expm1f16(x),
FE_INEXACT);

EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_UPWARD(static_cast<float16>(-0x1.ffcp-1),
LIBC_NAMESPACE::expm1f16(x),
FE_INEXACT);

EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_DOWNWARD(
static_cast<float16>(-1.0), LIBC_NAMESPACE::expm1f16(x), FE_INEXACT);

EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_TOWARD_ZERO(
static_cast<float16>(-0x1.ffcp-1), LIBC_NAMESPACE::expm1f16(x),
FE_INEXACT);

x = static_cast<float16>(-0x1.0a4p+3);

EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_NEAREST(
static_cast<float16>(-1.0), LIBC_NAMESPACE::expm1f16(x), FE_INEXACT);

EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_UPWARD(static_cast<float16>(-0x1.ffcp-1),
LIBC_NAMESPACE::expm1f16(x),
FE_INEXACT);

EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_DOWNWARD(
static_cast<float16>(-1.0), LIBC_NAMESPACE::expm1f16(x), FE_INEXACT);

EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_TOWARD_ZERO(
static_cast<float16>(-0x1.ffcp-1), LIBC_NAMESPACE::expm1f16(x),
FE_INEXACT);
}