[libc] Add implementation of expm1f.

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
This commit is contained in:
Tue Ly 2021-04-23 00:38:59 -04:00
parent c0e6f2f43a
commit 4e5f8b4d8d
21 changed files with 446 additions and 2 deletions

View File

@ -68,6 +68,7 @@ set(TARGET_LIBM_ENTRYPOINTS
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

View File

@ -69,6 +69,7 @@ set(TARGET_LIBM_ENTRYPOINTS
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

View File

@ -396,6 +396,7 @@ def StdC : StandardSpec<"stdc"> {
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>]>,

View File

@ -79,6 +79,8 @@ add_math_entrypoint_object(expf)
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)

18
libc/src/math/expm1f.h Normal file
View File

@ -0,0 +1,18 @@
//===-- 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

View File

@ -489,6 +489,18 @@ add_entrypoint_object(
libc.include.math
)
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

View File

@ -0,0 +1,57 @@
//===-- 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

View File

@ -1168,6 +1168,20 @@ add_fp_unittest(
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)

View File

@ -106,3 +106,23 @@ add_diff_binary(
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
)

View File

@ -0,0 +1,16 @@
//===-- 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")

View File

@ -0,0 +1,16 @@
//===-- 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")

View File

@ -38,3 +38,16 @@ add_fp_unittest(
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
)

View File

@ -0,0 +1,28 @@
//===-- 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);
}

View File

@ -0,0 +1,141 @@
//===-- 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);
}
}

View File

@ -32,6 +32,7 @@ template <> struct BitPatterns<float> {
static constexpr BitsType negZero = 0x80000000U;
static constexpr BitsType one = 0x3f800000U;
static constexpr BitsType negOne = 0xbf800000U;
// Examples of quiet NAN.
static constexpr BitsType aQuietNaN = 0x7fc00000U;

View File

@ -27,6 +27,7 @@ add_header_library(
ManipulationFunctions.h
NearestIntegerOperations.h
NormalFloat.h
PolyEval.h
DEPENDS
libc.include.math
libc.include.errno

View File

@ -0,0 +1,54 @@
//===-- 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

View File

@ -69,6 +69,4 @@ static inline cpp::EnableIfType<cpp::IsSame<T, float>::Value, T> fma(T x, T y,
} // namespace fputil
} // namespace __llvm_libc
#endif // Generic fma implementations
#endif // LLVM_LIBC_UTILS_FPUTIL_GENERIC_FMA_H

View File

@ -139,6 +139,12 @@ public:
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);
@ -309,6 +315,8 @@ unaryOperation(Operation op, InputType input) {
return mpfrInput.exp();
case Operation::Exp2:
return mpfrInput.exp2();
case Operation::Expm1:
return mpfrInput.expm1();
case Operation::Floor:
return mpfrInput.floor();
case Operation::Round:

View File

@ -28,6 +28,7 @@ enum class Operation : int {
Cos,
Exp,
Exp2,
Expm1,
Floor,
Round,
Sin,

View File

@ -0,0 +1,41 @@
# 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)))))