Use expm1f(x) = exp(x) - 1 for |x| > ln(2).
For |x| <= ln(2), divide it into 3 subintervals: [-ln2, -1/8], [-1/8, 1/8], [1/8, ln2]
and use a degree-6 polynomial approximation generated by Sollya's fpminmax for each interval.
Errors < 1.5 ULPs when we use fma to evaluate the polynomials.
Differential Revision: https://reviews.llvm.org/D101134
libc.src.math.cosf
libc.src.math.expf
libc.src.math.exp2f
+ libc.src.math.expm1f
libc.src.math.fabs
libc.src.math.fabsf
libc.src.math.fabsl
libc.src.math.cosf
libc.src.math.expf
libc.src.math.exp2f
+ libc.src.math.expm1f
libc.src.math.fabs
libc.src.math.fabsf
libc.src.math.fabsl
FunctionSpec<"expf", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
FunctionSpec<"exp2f", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
+ FunctionSpec<"expm1f", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
FunctionSpec<"remainderf", RetValSpec<FloatType>, [ArgSpec<FloatType>, ArgSpec<FloatType>]>,
FunctionSpec<"remainder", RetValSpec<DoubleType>, [ArgSpec<DoubleType>, ArgSpec<DoubleType>]>,
add_math_entrypoint_object(exp2f)
+add_math_entrypoint_object(expm1f)
+
add_math_entrypoint_object(fabs)
add_math_entrypoint_object(fabsf)
add_math_entrypoint_object(fabsl)
--- /dev/null
+//===-- Implementation header for expm1f ------------------------*- 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_EXPM1F_H
+#define LLVM_LIBC_SRC_MATH_EXPM1F_H
+
+namespace __llvm_libc {
+
+float expm1f(float x);
+
+} // namespace __llvm_libc
+
+#endif // LLVM_LIBC_SRC_MATH_EXPM1F_H
)
add_entrypoint_object(
+ expm1f
+ SRCS
+ expm1f.cpp
+ HDRS
+ ../expm1f.h
+ DEPENDS
+ libc.include.math
+ libc.src.math.expf
+ libc.src.math.fabsf
+)
+
+add_entrypoint_object(
copysign
SRCS
copysign.cpp
--- /dev/null
+//===-- Implementation of expm1f 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/expm1f.h"
+#include "src/__support/common.h"
+#include "src/math/expf.h"
+#include "utils/FPUtil/BasicOperations.h"
+#include "utils/FPUtil/PolyEval.h"
+
+namespace __llvm_libc {
+
+// When |x| > Ln2, catastrophic cancellation does not occur with the
+// subtraction expf(x) - 1.0f, so we use it to compute expm1f(x).
+//
+// We divide [-Ln2; Ln2] into 3 subintervals [-Ln2; -1/8], [-1/8; 1/8],
+// [1/8; Ln2]. And we use a degree-6 polynomial to approximate exp(x) - 1 in
+// each interval. The coefficients were generated by Sollya's fpminmax.
+//
+// See libc/utils/mathtools/expm1f.sollya for more detail.
+LLVM_LIBC_FUNCTION(float, expm1f, (float x)) {
+ const float Ln2 =
+ 0.69314718055994530941723212145817656807550013436025f; // For C++17:
+ // 0x1.62e'42ffp-1
+ float abs_x = __llvm_libc::fputil::abs(x);
+
+ if (abs_x <= Ln2) {
+ if (abs_x <= 0.125f) {
+ return x * __llvm_libc::fputil::polyeval(
+ x, 1.0f, 0.5f, 0.16666664183139801025390625f,
+ 4.1666664183139801025390625e-2f,
+ 8.3379410207271575927734375e-3f,
+ 1.3894210569560527801513671875e-3f);
+ }
+ if (x > 0.125f) {
+ return __llvm_libc::fputil::polyeval(
+ x, 1.23142086749794543720781803131103515625e-7f,
+ 0.9999969005584716796875f, 0.500031292438507080078125f,
+ 0.16650259494781494140625f, 4.21491153538227081298828125e-2f,
+ 7.53940828144550323486328125e-3f,
+ 2.05591344274580478668212890625e-3f);
+ }
+ return __llvm_libc::fputil::polyeval(
+ x, -6.899231408397099585272371768951416015625e-8f,
+ 0.999998271465301513671875f, 0.4999825656414031982421875f,
+ 0.16657467186450958251953125f, 4.1390590369701385498046875e-2f,
+ 7.856394164264202117919921875e-3f,
+ 9.380675037391483783721923828125e-4f);
+ }
+ return expf(x) - 1.0f;
+}
+
+} // namespace __llvm_libc
libc.utils.FPUtil.fputil
)
+add_fp_unittest(
+ expm1f_test
+ NEED_MPFR
+ SUITE
+ libc_math_unittests
+ SRCS
+ expm1f_test.cpp
+ DEPENDS
+ libc.include.errno
+ libc.include.math
+ libc.src.math.expm1f
+ libc.utils.FPUtil.fputil
+)
+
add_subdirectory(generic)
add_subdirectory(exhaustive)
add_subdirectory(differential_testing)
COMPILE_OPTIONS
-fno-builtin
)
+
+add_diff_binary(
+ expm1f_diff
+ SRCS
+ expm1f_diff.cpp
+ DEPENDS
+ .single_input_single_output_diff
+ libc.src.math.expm1f
+)
+
+add_diff_binary(
+ expm1f_perf
+ SRCS
+ expm1f_perf.cpp
+ DEPENDS
+ .single_input_single_output_diff
+ libc.src.math.expm1f
+ COMPILE_OPTIONS
+ -fno-builtin
+)
--- /dev/null
+//===-- Differential test for expm1f --------------------------------------===//
+//
+// 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 "SingleInputSingleOutputDiff.h"
+
+#include "src/math/expm1f.h"
+
+#include <math.h>
+
+SINGLE_INPUT_SINGLE_OUTPUT_DIFF(float, __llvm_libc::expm1f, ::expm1f,
+ "expm1f_diff.log")
--- /dev/null
+//===-- Differential test for expm1f --------------------------------------===//
+//
+// 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 "SingleInputSingleOutputDiff.h"
+
+#include "src/math/expm1f.h"
+
+#include <math.h>
+
+SINGLE_INPUT_SINGLE_OUTPUT_PERF(float, __llvm_libc::expm1f, ::expm1f,
+ "expm1f_perf.log")
libc.src.math.cosf
libc.utils.FPUtil.fputil
)
+
+add_fp_unittest(
+ expm1f_test
+ NEED_MPFR
+ SUITE
+ libc_math_exhaustive_tests
+ SRCS
+ expm1f_test.cpp
+ DEPENDS
+ libc.include.math
+ libc.src.math.expm1f
+ libc.utils.FPUtil.fputil
+)
--- /dev/null
+//===-- Exhaustive test for expm1f-----------------------------------------===//
+//
+// 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/expm1f.h"
+#include "utils/FPUtil/FPBits.h"
+#include "utils/FPUtil/TestHelpers.h"
+#include "utils/MPFRWrapper/MPFRUtils.h"
+#include <math.h>
+
+using FPBits = __llvm_libc::fputil::FPBits<float>;
+
+namespace mpfr = __llvm_libc::testing::mpfr;
+
+TEST(LlvmLibcExpm1fExhaustiveTest, AllValues) {
+ uint32_t bits = 0;
+ do {
+ FPBits x(bits);
+ if (!x.isInfOrNaN() && float(x) < 88.70f) {
+ ASSERT_MPFR_MATCH(mpfr::Operation::Expm1, float(x),
+ __llvm_libc::expm1f(float(x)), 1.5);
+ }
+ } while (bits++ < 0xffff'ffffU);
+}
--- /dev/null
+//===-- Unittests for expm1f-----------------------------------------------===//
+//
+// 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/expm1f.h"
+#include "utils/FPUtil/BitPatterns.h"
+#include "utils/FPUtil/ClassificationFunctions.h"
+#include "utils/FPUtil/FloatOperations.h"
+#include "utils/FPUtil/FloatProperties.h"
+#include "utils/MPFRWrapper/MPFRUtils.h"
+#include "utils/UnitTest/Test.h"
+#include <math.h>
+
+#include <errno.h>
+#include <stdint.h>
+
+using __llvm_libc::fputil::isNegativeQuietNaN;
+using __llvm_libc::fputil::isQuietNaN;
+using __llvm_libc::fputil::valueAsBits;
+using __llvm_libc::fputil::valueFromBits;
+
+using BitPatterns = __llvm_libc::fputil::BitPatterns<float>;
+
+namespace mpfr = __llvm_libc::testing::mpfr;
+
+TEST(LlvmLibcExpm1fTest, SpecialNumbers) {
+ errno = 0;
+
+ EXPECT_TRUE(
+ isQuietNaN(__llvm_libc::expm1f(valueFromBits(BitPatterns::aQuietNaN))));
+ EXPECT_EQ(errno, 0);
+
+ EXPECT_TRUE(isNegativeQuietNaN(
+ __llvm_libc::expm1f(valueFromBits(BitPatterns::aNegativeQuietNaN))));
+ EXPECT_EQ(errno, 0);
+
+ EXPECT_TRUE(isQuietNaN(
+ __llvm_libc::expm1f(valueFromBits(BitPatterns::aSignallingNaN))));
+ EXPECT_EQ(errno, 0);
+
+ EXPECT_TRUE(isNegativeQuietNaN(
+ __llvm_libc::expm1f(valueFromBits(BitPatterns::aNegativeSignallingNaN))));
+ EXPECT_EQ(errno, 0);
+
+ EXPECT_EQ(BitPatterns::inf,
+ valueAsBits(__llvm_libc::expm1f(valueFromBits(BitPatterns::inf))));
+ EXPECT_EQ(errno, 0);
+
+ EXPECT_EQ(BitPatterns::negOne, valueAsBits(__llvm_libc::expm1f(
+ valueFromBits(BitPatterns::negInf))));
+ EXPECT_EQ(errno, 0);
+
+ EXPECT_EQ(BitPatterns::zero,
+ valueAsBits(__llvm_libc::expm1f(valueFromBits(BitPatterns::zero))));
+ EXPECT_EQ(errno, 0);
+
+ EXPECT_EQ(BitPatterns::negZero, valueAsBits(__llvm_libc::expm1f(
+ valueFromBits(BitPatterns::negZero))));
+ EXPECT_EQ(errno, 0);
+}
+
+TEST(LlvmLibcExpm1fTest, Overflow) {
+ errno = 0;
+ EXPECT_EQ(BitPatterns::inf,
+ valueAsBits(__llvm_libc::expm1f(valueFromBits(0x7f7fffffU))));
+ EXPECT_EQ(errno, ERANGE);
+
+ errno = 0;
+ EXPECT_EQ(BitPatterns::inf,
+ valueAsBits(__llvm_libc::expm1f(valueFromBits(0x42cffff8U))));
+ EXPECT_EQ(errno, ERANGE);
+
+ errno = 0;
+ EXPECT_EQ(BitPatterns::inf,
+ valueAsBits(__llvm_libc::expm1f(valueFromBits(0x42d00008U))));
+ EXPECT_EQ(errno, ERANGE);
+}
+
+TEST(LlvmLibcExpm1fTest, Underflow) {
+ errno = 0;
+ EXPECT_EQ(BitPatterns::negOne,
+ valueAsBits(__llvm_libc::expm1f(valueFromBits(0xff7fffffU))));
+ EXPECT_EQ(errno, ERANGE);
+
+ errno = 0;
+ EXPECT_EQ(BitPatterns::negOne,
+ valueAsBits(__llvm_libc::expm1f(valueFromBits(0xc2cffff8U))));
+ EXPECT_EQ(errno, ERANGE);
+
+ errno = 0;
+ EXPECT_EQ(BitPatterns::negOne,
+ valueAsBits(__llvm_libc::expm1f(valueFromBits(0xc2d00008U))));
+ EXPECT_EQ(errno, ERANGE);
+}
+
+// Test with inputs which are the borders of underflow/overflow but still
+// produce valid results without setting errno.
+TEST(LlvmLibcExpm1fTest, Borderline) {
+ float x;
+
+ errno = 0;
+ x = valueFromBits(0x42affff8U);
+ ASSERT_MPFR_MATCH(mpfr::Operation::Expm1, x, __llvm_libc::expm1f(x), 1.0);
+ EXPECT_EQ(errno, 0);
+
+ x = valueFromBits(0x42b00008U);
+ ASSERT_MPFR_MATCH(mpfr::Operation::Expm1, x, __llvm_libc::expm1f(x), 1.0);
+ EXPECT_EQ(errno, 0);
+
+ x = valueFromBits(0xc2affff8U);
+ ASSERT_MPFR_MATCH(mpfr::Operation::Expm1, x, __llvm_libc::expm1f(x), 1.0);
+ EXPECT_EQ(errno, 0);
+
+ x = valueFromBits(0xc2b00008U);
+ ASSERT_MPFR_MATCH(mpfr::Operation::Expm1, x, __llvm_libc::expm1f(x), 1.0);
+ EXPECT_EQ(errno, 0);
+}
+
+TEST(LlvmLibcExpm1fTest, InFloatRange) {
+ constexpr uint32_t count = 1000000;
+ constexpr uint32_t step = UINT32_MAX / count;
+ for (uint32_t i = 0, v = 0; i <= count; ++i, v += step) {
+ float x = valueFromBits(v);
+ if (isnan(x) || isinf(x))
+ continue;
+ errno = 0;
+ float result = __llvm_libc::expm1f(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) || errno != 0)
+ continue;
+ ASSERT_MPFR_MATCH(mpfr::Operation::Expm1, x, __llvm_libc::expm1f(x), 1.5);
+ }
+}
static constexpr BitsType negZero = 0x80000000U;
static constexpr BitsType one = 0x3f800000U;
+ static constexpr BitsType negOne = 0xbf800000U;
// Examples of quiet NAN.
static constexpr BitsType aQuietNaN = 0x7fc00000U;
ManipulationFunctions.h
NearestIntegerOperations.h
NormalFloat.h
+ PolyEval.h
DEPENDS
libc.include.math
libc.include.errno
--- /dev/null
+//===-- Common header for PolyEval implementations --------------*- 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_UTILS_FPUTIL_POLYEVAL_H
+#define LLVM_LIBC_UTILS_FPUTIL_POLYEVAL_H
+
+#include "utils/CPP/TypeTraits.h"
+
+// Evaluate polynomial using Horner's Scheme:
+// With polyeval(x, a_0, a_1, ..., a_n) = a_n * x^n + ... + a_1 * x + a_0, we
+// evaluated it as: a_0 + x * (a_1 + x * ( ... (a_(n-1) + x * a_n) ... ) ) ).
+// We will use fma instructions if available.
+// Example: to evaluate x^3 + 2*x^2 + 3*x + 4, call
+// polyeval( x, 4.0, 3.0, 2.0, 1.0 )
+
+#if defined(__x86_64__) || defined(__aarch64__)
+#include "FMA.h"
+
+namespace __llvm_libc {
+namespace fputil {
+
+template <typename T> static inline T polyeval(T x, T a0) { return a0; }
+
+template <typename T, typename... Ts>
+static inline T polyeval(T x, T a0, Ts... a) {
+ return fma(x, polyeval(x, a...), a0);
+}
+
+} // namespace fputil
+} // namespace __llvm_libc
+
+#else
+
+namespace __llvm_libc {
+namespace fputil {
+
+template <typename T> static inline T polyeval(T x, T a0) { return a0; }
+
+template <typename T, typename... Ts>
+static inline T polyeval(T x, T a0, Ts... a) {
+ return x * polyeval(x, a...) + a0;
+}
+
+} // namespace fputil
+} // namespace __llvm_libc
+
+#endif
+
+#endif // LLVM_LIBC_UTILS_FPUTIL_FMA_H
} // namespace fputil
} // namespace __llvm_libc
-#endif // Generic fma implementations
-
#endif // LLVM_LIBC_UTILS_FPUTIL_GENERIC_FMA_H
return result;
}
+ MPFRNumber expm1() const {
+ MPFRNumber result;
+ mpfr_expm1(result.value, value, MPFR_RNDN);
+ return result;
+ }
+
MPFRNumber floor() const {
MPFRNumber result;
mpfr_floor(result.value, value);
return mpfrInput.exp();
case Operation::Exp2:
return mpfrInput.exp2();
+ case Operation::Expm1:
+ return mpfrInput.expm1();
case Operation::Floor:
return mpfrInput.floor();
case Operation::Round:
Cos,
Exp,
Exp2,
+ Expm1,
Floor,
Round,
Sin,
--- /dev/null
+# Scripts to generate polynomial approximations for expm1f function using Sollya.
+#
+# To compute expm1f(x), for |x| > Ln(2), using expf(x) - 1.0f is accurate enough, since catastrophic
+# cancellation does not occur with the subtraction.
+#
+# For |x| <= Ln(2), we divide [-Ln2; Ln2] into 3 subintervals: [-Ln2; -1/8], [-1/8, 1/8], [1/8, Ln2],
+# and use a degree-6 polynomial to approximate expm1f in each interval.
+
+> f := expm1(x);
+
+# Polynomial approximation for e^(x) - 1 on [-Ln2, -1/8].
+> P1 := fpminmax(f, [|0, ..., 6|], [|24...], [-log(2), -1/8], relative);
+
+> log2(supnorm(P1, f, [-log(2), -1/8], relative, 2^(-50)));
+[-29.718757839645220560605567049447893449270454705067;-29.7187578396452193192777968211678241631166415833034]
+
+> P1;
+-6.899231408397099585272371768951416015625e-8 + x * (0.999998271465301513671875 + x * (0.4999825656414031982421875
++ x * (0.16657467186450958251953125 + x * (4.1390590369701385498046875e-2 + x * (7.856394164264202117919921875e-3
++ x * 9.380675037391483783721923828125e-4)))))
+
+# Polynomial approximation for e^(x) - 1 on [-1/8, 1/8].
+> P2 := fpminimax(f, [|1,...,6|], [|24...|], [-1/8, 1/8], relative);
+
+> log2(supnorm(P2, f, [-1/8, 1/8], relative, 2^(-50)));
+[-34.542864999883718873453825391741639571826398336605;-34.542864999883717632126055163461570285672585214842]
+
+> P2;
+x * (1 + x * (0.5 + x * (0.16666664183139801025390625 + x * (4.1666664183139801025390625e-2
++ x * (8.3379410207271575927734375e-3 + x * 1.3894210569560527801513671875e-3)))))
+
+# Polynomial approximation for e^(x) - 1 on [1/8, Ln2].
+> P3 := fpminimax(f, [|0,...,6|], [|24...|], [1/8, log(2)], relative);
+
+> log2(supnorm(P3, f, [1/8, log(2)], relative, 2^(-50)));
+[-29.189438260653683379922869677995123967174571911561;-29.1894382606536821385950994497150546810207587897976]
+
+> P3;
+1.23142086749794543720781803131103515625e-7 + x * (0.9999969005584716796875 + x * (0.500031292438507080078125
++ x * (0.16650259494781494140625 + x * (4.21491153538227081298828125e-2 + x * (7.53940828144550323486328125e-3
++ x * 2.05591344274580478668212890625e-3)))))