[libc] Add implementation of expm1f.
authorTue Ly <lntue@google.com>
Fri, 23 Apr 2021 04:38:59 +0000 (00:38 -0400)
committerTue Ly <lntue@google.com>
Thu, 10 Jun 2021 18:58:34 +0000 (14:58 -0400)
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

21 files changed:
libc/config/linux/aarch64/entrypoints.txt
libc/config/linux/x86_64/entrypoints.txt
libc/spec/stdc.td
libc/src/math/CMakeLists.txt
libc/src/math/expm1f.h [new file with mode: 0644]
libc/src/math/generic/CMakeLists.txt
libc/src/math/generic/expm1f.cpp [new file with mode: 0644]
libc/test/src/math/CMakeLists.txt
libc/test/src/math/differential_testing/CMakeLists.txt
libc/test/src/math/differential_testing/expm1f_diff.cpp [new file with mode: 0644]
libc/test/src/math/differential_testing/expm1f_perf.cpp [new file with mode: 0644]
libc/test/src/math/exhaustive/CMakeLists.txt
libc/test/src/math/exhaustive/expm1f_test.cpp [new file with mode: 0644]
libc/test/src/math/expm1f_test.cpp [new file with mode: 0644]
libc/utils/FPUtil/BitPatterns.h
libc/utils/FPUtil/CMakeLists.txt
libc/utils/FPUtil/PolyEval.h [new file with mode: 0644]
libc/utils/FPUtil/generic/FMA.h
libc/utils/MPFRWrapper/MPFRUtils.cpp
libc/utils/MPFRWrapper/MPFRUtils.h
libc/utils/mathtools/expm1f.sollya [new file with mode: 0644]

index 211d918..95e8b8c 100644 (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
index 331ea16..5143864 100644 (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
index c2a9045..c662181 100644 (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>]>,
index e2abe21..ddee3ca 100644 (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)
diff --git a/libc/src/math/expm1f.h b/libc/src/math/expm1f.h
new file mode 100644 (file)
index 0000000..2577e4a
--- /dev/null
@@ -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
index 45dcc5a..686d275 100644 (file)
@@ -490,6 +490,18 @@ add_entrypoint_object(
 )
 
 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
diff --git a/libc/src/math/generic/expm1f.cpp b/libc/src/math/generic/expm1f.cpp
new file mode 100644 (file)
index 0000000..61ebbf2
--- /dev/null
@@ -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
index 49b5290..be06839 100644 (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)
index 94a0aa6..82f912c 100644 (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
+)
diff --git a/libc/test/src/math/differential_testing/expm1f_diff.cpp b/libc/test/src/math/differential_testing/expm1f_diff.cpp
new file mode 100644 (file)
index 0000000..e12072e
--- /dev/null
@@ -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")
diff --git a/libc/test/src/math/differential_testing/expm1f_perf.cpp b/libc/test/src/math/differential_testing/expm1f_perf.cpp
new file mode 100644 (file)
index 0000000..9e8e2df
--- /dev/null
@@ -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")
index e7b5b30..f6580da 100644 (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
+)
diff --git a/libc/test/src/math/exhaustive/expm1f_test.cpp b/libc/test/src/math/exhaustive/expm1f_test.cpp
new file mode 100644 (file)
index 0000000..30d588f
--- /dev/null
@@ -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);
+}
diff --git a/libc/test/src/math/expm1f_test.cpp b/libc/test/src/math/expm1f_test.cpp
new file mode 100644 (file)
index 0000000..d4b06c2
--- /dev/null
@@ -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);
+  }
+}
index 439ccbc..a26d403 100644 (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;
index c4600fd..75fa89d 100644 (file)
@@ -27,6 +27,7 @@ add_header_library(
     ManipulationFunctions.h
     NearestIntegerOperations.h
     NormalFloat.h
+    PolyEval.h
   DEPENDS
     libc.include.math
     libc.include.errno
diff --git a/libc/utils/FPUtil/PolyEval.h b/libc/utils/FPUtil/PolyEval.h
new file mode 100644 (file)
index 0000000..ce8377f
--- /dev/null
@@ -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
index f88e289..9471830 100644 (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
index 7eb2e4f..e040393 100644 (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:
index 604a20d..ee98c8f 100644 (file)
@@ -28,6 +28,7 @@ enum class Operation : int {
   Cos,
   Exp,
   Exp2,
+  Expm1,
   Floor,
   Round,
   Sin,
diff --git a/libc/utils/mathtools/expm1f.sollya b/libc/utils/mathtools/expm1f.sollya
new file mode 100644 (file)
index 0000000..d9dff4d
--- /dev/null
@@ -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)))))