69 changes: 69 additions & 0 deletions libc/src/math/expf.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//===-- Single-precision e^x 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 "exp_utils.h"
#include "math_utils.h"

#include "include/math.h"
#include "src/__support/common.h"

#include <stdint.h>

#define InvLn2N exp2f_data.invln2_scaled
#define T exp2f_data.tab
#define C exp2f_data.poly_scaled
#define SHIFT exp2f_data.shift

namespace __llvm_libc {

float LLVM_LIBC_ENTRYPOINT(expf)(float x) {
uint32_t abstop;
uint64_t ki, t;
// double_t for better performance on targets with FLT_EVAL_METHOD == 2.
double_t kd, xd, z, r, r2, y, s;

xd = static_cast<double_t>(x);
abstop = top12_bits(x) & 0x7ff;
if (unlikely(abstop >= top12_bits(88.0f))) {
// |x| >= 88 or x is nan.
if (as_uint32_bits(x) == as_uint32_bits(-INFINITY))
return 0.0f;
if (abstop >= top12_bits(INFINITY))
return x + x;
if (x > as_float(0x42b17217)) // x > log(0x1p128) ~= 88.72
return overflow<float>(0);
if (x < as_float(0xc2cff1b4)) // x < log(0x1p-150) ~= -103.97
return underflow<float>(0);
if (x < as_float(0xc2ce8ecf)) // x < log(0x1p-149) ~= -103.28
return may_underflow<float>(0);
}

// x*N/Ln2 = k + r with r in [-1/2, 1/2] and int k.
z = InvLn2N * xd;

// Round and convert z to int, the result is in [-150*N, 128*N] and
// ideally nearest int is used, otherwise the magnitude of r can be
// bigger which gives larger approximation error.
kd = static_cast<double>(z + SHIFT);
ki = as_uint64_bits(kd);
kd -= SHIFT;
r = z - kd;

// exp(x) = 2^(k/N) * 2^(r/N) ~= s *(C0*r^3 + C1*r^2 + C2*r + 1)
t = T[ki % N];
t += ki << (52 - EXP2F_TABLE_BITS);
s = as_double(t);
z = C[0] * r + C[1];
r2 = r * r;
y = C[2] * r + 1;
y = z * r2 + y;
y = y * s;
return static_cast<float>(y);
}

} // namespace __llvm_libc
18 changes: 18 additions & 0 deletions libc/src/math/expf.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//===-- Implementation header for expf --------------------------*- 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_EXPF_H
#define LLVM_LIBC_SRC_MATH_EXPF_H

namespace __llvm_libc {

float expf(float x);

} // namespace __llvm_libc

#endif // LLVM_LIBC_SRC_MATH_EXPF_H
27 changes: 27 additions & 0 deletions libc/src/math/math_utils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//===-- Implementation of math utils --------------------------------------===//
//
// 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 "math_utils.h"

namespace __llvm_libc {

const float XFlowValues<float>::overflow_value =
as_float(0x70000000); // 0x1p97f
const float XFlowValues<float>::underflow_value =
as_float(0x10000000); // 0x1p97f
const float XFlowValues<float>::may_underflow_value =
as_float(0x1a200000); // 0x1.4p-75f

const double XFlowValues<double>::overflow_value =
as_double(0x7000000000000000); // 0x1p769
const double XFlowValues<double>::underflow_value =
as_double(0x1000000000000000); // 0x1p-767
const double XFlowValues<double>::may_underflow_value =
as_double(0x1e58000000000000); // 0x1.8p-538

} // namespace __llvm_libc
82 changes: 71 additions & 11 deletions libc/src/math/math_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,22 @@

#include "include/errno.h"
#include "include/math.h"

#include "src/__support/common.h"
#include "src/errno/llvmlibc_errno.h"
#include "utils/CPP/TypeTraits.h"

#include <stdint.h>

namespace __llvm_libc {

static inline float with_errnof(float x, int err) {
if (math_errhandling & MATH_ERRNO)
llvmlibc_errno = err;
return x;
}

static inline uint32_t as_uint32_bits(float x) {
return *reinterpret_cast<uint32_t *>(&x);
}

static inline uint64_t as_uint64_bits(double x) {
return *reinterpret_cast<uint64_t *>(&x);
}

static inline float as_float(uint32_t x) {
return *reinterpret_cast<float *>(&x);
}
Expand All @@ -37,12 +35,74 @@ static inline double as_double(uint64_t x) {
return *reinterpret_cast<double *>(&x);
}

static inline constexpr float invalidf(float x) {
float y = (x - x) / (x - x);
return isnan(x) ? y : with_errnof(y, EDOM);
static inline uint32_t top12_bits(float x) { return as_uint32_bits(x) >> 20; }

static inline uint32_t top12_bits(double x) { return as_uint64_bits(x) >> 52; }

// Values to trigger underflow and overflow.
template <typename T> struct XFlowValues;

template <> struct XFlowValues<float> {
static const float overflow_value;
static const float underflow_value;
static const float may_underflow_value;
};

template <> struct XFlowValues<double> {
static const double overflow_value;
static const double underflow_value;
static const double may_underflow_value;
};

template <typename T> static inline T with_errno(T x, int err) {
if (math_errhandling & MATH_ERRNO)
llvmlibc_errno = err;
return x;
}

static inline void force_eval_float(float x) { volatile float y UNUSED = x; }
template <typename T> static inline void force_eval(T x) {
volatile T y UNUSED = x;
}

template <typename T> static inline T opt_barrier(T x) {
volatile T y = x;
return y;
}

template <typename T> struct IsFloatOrDouble {
static constexpr bool Value =
cpp::IsSame<T, float>::Value || cpp::IsSame<T, double>::Value;
};

template <typename T>
using EnableIfFloatOrDouble = cpp::EnableIfType<IsFloatOrDouble<T>::Value, int>;

template <typename T, EnableIfFloatOrDouble<T> = 0>
T xflow(uint32_t sign, T y) {
// Underflow happens when two extremely small values are multiplied.
// Likewise, overflow happens when two large values are mulitplied.
y = opt_barrier(sign ? -y : y) * y;
return with_errno(y, ERANGE);
}

template <typename T, EnableIfFloatOrDouble<T> = 0> T overflow(uint32_t sign) {
return xflow(sign, XFlowValues<T>::overflow_value);
}

template <typename T, EnableIfFloatOrDouble<T> = 0> T underflow(uint32_t sign) {
return xflow(sign, XFlowValues<T>::underflow_value);
}

template <typename T, EnableIfFloatOrDouble<T> = 0>
T may_underflow(uint32_t sign) {
return xflow(sign, XFlowValues<T>::may_underflow_value);
}

template <typename T, EnableIfFloatOrDouble<T> = 0>
static inline constexpr float invalid(T x) {
T y = (x - x) / (x - x);
return isnan(x) ? y : with_errno(y, EDOM);
}

} // namespace __llvm_libc

Expand Down
4 changes: 2 additions & 2 deletions libc/src/math/sincosf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ void LLVM_LIBC_ENTRYPOINT(sincosf)(float y, float *sinp, float *cosp) {
if (unlikely(abstop12(y) < abstop12(as_float(0x39800000)))) {
if (unlikely(abstop12(y) < abstop12(as_float(0x800000))))
// Force underflow for tiny y.
force_eval_float(x2);
force_eval<float>(x2);
*sinp = y;
*cosp = 1.0f;
return;
Expand Down Expand Up @@ -69,7 +69,7 @@ void LLVM_LIBC_ENTRYPOINT(sincosf)(float y, float *sinp, float *cosp) {
// Needed to set errno for +-Inf, the add is a hack to work
// around a gcc register allocation issue: just passing y
// affects code generation in the fast path.
invalidf(y + y);
invalid(y + y);
}
}

Expand Down
4 changes: 2 additions & 2 deletions libc/src/math/sinf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ float LLVM_LIBC_ENTRYPOINT(sinf)(float y) {
if (unlikely(abstop12(y) < abstop12(as_float(0x39800000)))) {
if (unlikely(abstop12(y) < abstop12(as_float(0x800000))))
// Force underflow for tiny y.
force_eval_float(s);
force_eval<float>(s);
return y;
}

Expand Down Expand Up @@ -62,7 +62,7 @@ float LLVM_LIBC_ENTRYPOINT(sinf)(float y) {
return sinf_poly(x * s, x * x, p, n);
}

return invalidf(y);
return invalid(y);
}

} // namespace __llvm_libc
28 changes: 28 additions & 0 deletions libc/test/src/math/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,31 @@ add_math_unittest(
libc.src.math.fabsf
libc.utils.FPUtil.fputil
)

add_math_unittest(
expf_test
NEED_MPFR
SUITE
libc_math_unittests
SRCS
expf_test.cpp
DEPENDS
libc.include.errno
libc.include.math
libc.src.math.expf
libc.utils.FPUtil.fputil
)

add_math_unittest(
exp2f_test
NEED_MPFR
SUITE
libc_math_unittests
SRCS
exp2f_test.cpp
DEPENDS
libc.include.errno
libc.include.math
libc.src.math.exp2f
libc.utils.FPUtil.fputil
)
154 changes: 154 additions & 0 deletions libc/test/src/math/exp2f_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//===-- Unittests for exp2f -----------------------------------------------===//
//
// 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/errno.h"
#include "include/math.h"
#include "src/errno/llvmlibc_errno.h"
#include "src/math/exp2f.h"
#include "utils/FPUtil/BitPatterns.h"
#include "utils/FPUtil/FloatOperations.h"
#include "utils/FPUtil/FloatProperties.h"
#include "utils/MPFRWrapper/MPFRUtils.h"
#include "utils/UnitTest/Test.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;

// 12 additional bits of precision over the base precision of a |float|
// value.
static constexpr mpfr::Tolerance tolerance{mpfr::Tolerance::floatPrecision, 12,
0xFFF};

TEST(exp2fTest, SpecialNumbers) {
llvmlibc_errno = 0;

EXPECT_TRUE(
isQuietNaN(__llvm_libc::exp2f(valueFromBits(BitPatterns::aQuietNaN))));
EXPECT_EQ(llvmlibc_errno, 0);

EXPECT_TRUE(isNegativeQuietNaN(
__llvm_libc::exp2f(valueFromBits(BitPatterns::aNegativeQuietNaN))));
EXPECT_EQ(llvmlibc_errno, 0);

EXPECT_TRUE(isQuietNaN(
__llvm_libc::exp2f(valueFromBits(BitPatterns::aSignallingNaN))));
EXPECT_EQ(llvmlibc_errno, 0);

EXPECT_TRUE(isNegativeQuietNaN(
__llvm_libc::exp2f(valueFromBits(BitPatterns::aNegativeSignallingNaN))));
EXPECT_EQ(llvmlibc_errno, 0);

EXPECT_EQ(BitPatterns::inf,
valueAsBits(__llvm_libc::exp2f(valueFromBits(BitPatterns::inf))));
EXPECT_EQ(llvmlibc_errno, 0);

EXPECT_EQ(BitPatterns::zero, valueAsBits(__llvm_libc::exp2f(
valueFromBits(BitPatterns::negInf))));
EXPECT_EQ(llvmlibc_errno, 0);

EXPECT_EQ(BitPatterns::one,
valueAsBits(__llvm_libc::exp2f(valueFromBits(BitPatterns::zero))));
EXPECT_EQ(llvmlibc_errno, 0);

EXPECT_EQ(BitPatterns::one, valueAsBits(__llvm_libc::exp2f(
valueFromBits(BitPatterns::negZero))));
EXPECT_EQ(llvmlibc_errno, 0);
}

TEST(ExpfTest, Overflow) {
llvmlibc_errno = 0;
EXPECT_EQ(BitPatterns::inf,
valueAsBits(__llvm_libc::exp2f(valueFromBits(0x7f7fffffU))));
EXPECT_EQ(llvmlibc_errno, ERANGE);

llvmlibc_errno = 0;
EXPECT_EQ(BitPatterns::inf,
valueAsBits(__llvm_libc::exp2f(valueFromBits(0x43000000U))));
EXPECT_EQ(llvmlibc_errno, ERANGE);

llvmlibc_errno = 0;
EXPECT_EQ(BitPatterns::inf,
valueAsBits(__llvm_libc::exp2f(valueFromBits(0x43000001U))));
EXPECT_EQ(llvmlibc_errno, ERANGE);
}

// Test with inputs which are the borders of underflow/overflow but still
// produce valid results without setting errno.
TEST(ExpfTest, Borderline) {
float x;

llvmlibc_errno = 0;
x = valueFromBits(0x42fa0001U);
EXPECT_MPFR_MATCH(mpfr::OP_Exp2, x, __llvm_libc::exp2f(x), tolerance);
EXPECT_EQ(llvmlibc_errno, 0);

x = valueFromBits(0x42ffffffU);
EXPECT_MPFR_MATCH(mpfr::OP_Exp2, x, __llvm_libc::exp2f(x), tolerance);
EXPECT_EQ(llvmlibc_errno, 0);

x = valueFromBits(0xc2fa0001U);
EXPECT_MPFR_MATCH(mpfr::OP_Exp2, x, __llvm_libc::exp2f(x), tolerance);
EXPECT_EQ(llvmlibc_errno, 0);

x = valueFromBits(0xc2fc0000U);
EXPECT_MPFR_MATCH(mpfr::OP_Exp2, x, __llvm_libc::exp2f(x), tolerance);
EXPECT_EQ(llvmlibc_errno, 0);

x = valueFromBits(0xc2fc0001U);
EXPECT_MPFR_MATCH(mpfr::OP_Exp2, x, __llvm_libc::exp2f(x), tolerance);
EXPECT_EQ(llvmlibc_errno, 0);

x = valueFromBits(0xc3150000U);
EXPECT_MPFR_MATCH(mpfr::OP_Exp2, x, __llvm_libc::exp2f(x), tolerance);
EXPECT_EQ(llvmlibc_errno, 0);
}

TEST(ExpfTest, Underflow) {
llvmlibc_errno = 0;
EXPECT_EQ(BitPatterns::zero,
valueAsBits(__llvm_libc::exp2f(valueFromBits(0xff7fffffU))));
EXPECT_EQ(llvmlibc_errno, ERANGE);

llvmlibc_errno = 0;
float x = valueFromBits(0xc3158000U);
EXPECT_MPFR_MATCH(mpfr::OP_Exp2, x, __llvm_libc::exp2f(x), tolerance);
EXPECT_EQ(llvmlibc_errno, ERANGE);

llvmlibc_errno = 0;
x = valueFromBits(0xc3165432U);
EXPECT_MPFR_MATCH(mpfr::OP_Exp2, x, __llvm_libc::exp2f(x), tolerance);
EXPECT_EQ(llvmlibc_errno, ERANGE);
}

TEST(exp2fTest, 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;
llvmlibc_errno = 0;
float result = __llvm_libc::exp2f(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) || llvmlibc_errno != 0)
continue;
ASSERT_MPFR_MATCH(mpfr::OP_Exp2, x, __llvm_libc::exp2f(x), tolerance);
}
}
146 changes: 146 additions & 0 deletions libc/test/src/math/expf_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//===-- Unittests for expf ------------------------------------------------===//
//
// 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/errno.h"
#include "include/math.h"
#include "src/errno/llvmlibc_errno.h"
#include "src/math/expf.h"
#include "utils/FPUtil/BitPatterns.h"
#include "utils/FPUtil/FloatOperations.h"
#include "utils/FPUtil/FloatProperties.h"
#include "utils/MPFRWrapper/MPFRUtils.h"
#include "utils/UnitTest/Test.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;

// 12 additional bits of precision over the base precision of a |float|
// value.
static constexpr mpfr::Tolerance tolerance{mpfr::Tolerance::floatPrecision, 12,
0xFFF};

TEST(ExpfTest, SpecialNumbers) {
llvmlibc_errno = 0;

EXPECT_TRUE(
isQuietNaN(__llvm_libc::expf(valueFromBits(BitPatterns::aQuietNaN))));
EXPECT_EQ(llvmlibc_errno, 0);

EXPECT_TRUE(isNegativeQuietNaN(
__llvm_libc::expf(valueFromBits(BitPatterns::aNegativeQuietNaN))));
EXPECT_EQ(llvmlibc_errno, 0);

EXPECT_TRUE(isQuietNaN(
__llvm_libc::expf(valueFromBits(BitPatterns::aSignallingNaN))));
EXPECT_EQ(llvmlibc_errno, 0);

EXPECT_TRUE(isNegativeQuietNaN(
__llvm_libc::expf(valueFromBits(BitPatterns::aNegativeSignallingNaN))));
EXPECT_EQ(llvmlibc_errno, 0);

EXPECT_EQ(BitPatterns::inf,
valueAsBits(__llvm_libc::expf(valueFromBits(BitPatterns::inf))));
EXPECT_EQ(llvmlibc_errno, 0);

EXPECT_EQ(BitPatterns::zero,
valueAsBits(__llvm_libc::expf(valueFromBits(BitPatterns::negInf))));
EXPECT_EQ(llvmlibc_errno, 0);

EXPECT_EQ(BitPatterns::one,
valueAsBits(__llvm_libc::expf(valueFromBits(BitPatterns::zero))));
EXPECT_EQ(llvmlibc_errno, 0);

EXPECT_EQ(BitPatterns::one, valueAsBits(__llvm_libc::expf(
valueFromBits(BitPatterns::negZero))));
EXPECT_EQ(llvmlibc_errno, 0);
}

TEST(ExpfTest, Overflow) {
llvmlibc_errno = 0;
EXPECT_EQ(BitPatterns::inf,
valueAsBits(__llvm_libc::expf(valueFromBits(0x7f7fffffU))));
EXPECT_EQ(llvmlibc_errno, ERANGE);

llvmlibc_errno = 0;
EXPECT_EQ(BitPatterns::inf,
valueAsBits(__llvm_libc::expf(valueFromBits(0x42cffff8U))));
EXPECT_EQ(llvmlibc_errno, ERANGE);

llvmlibc_errno = 0;
EXPECT_EQ(BitPatterns::inf,
valueAsBits(__llvm_libc::expf(valueFromBits(0x42d00008U))));
EXPECT_EQ(llvmlibc_errno, ERANGE);
}

TEST(ExpfTest, Underflow) {
llvmlibc_errno = 0;
EXPECT_EQ(BitPatterns::zero,
valueAsBits(__llvm_libc::expf(valueFromBits(0xff7fffffU))));
EXPECT_EQ(llvmlibc_errno, ERANGE);

llvmlibc_errno = 0;
EXPECT_EQ(BitPatterns::zero,
valueAsBits(__llvm_libc::expf(valueFromBits(0xc2cffff8U))));
EXPECT_EQ(llvmlibc_errno, ERANGE);

llvmlibc_errno = 0;
EXPECT_EQ(BitPatterns::zero,
valueAsBits(__llvm_libc::expf(valueFromBits(0xc2d00008U))));
EXPECT_EQ(llvmlibc_errno, ERANGE);
}

// Test with inputs which are the borders of underflow/overflow but still
// produce valid results without setting errno.
TEST(ExpfTest, Borderline) {
float x;

llvmlibc_errno = 0;
x = valueFromBits(0x42affff8U);
ASSERT_MPFR_MATCH(mpfr::OP_Exp, x, __llvm_libc::expf(x), tolerance);
EXPECT_EQ(llvmlibc_errno, 0);

x = valueFromBits(0x42b00008U);
ASSERT_MPFR_MATCH(mpfr::OP_Exp, x, __llvm_libc::expf(x), tolerance);
EXPECT_EQ(llvmlibc_errno, 0);

x = valueFromBits(0xc2affff8U);
ASSERT_MPFR_MATCH(mpfr::OP_Exp, x, __llvm_libc::expf(x), tolerance);
EXPECT_EQ(llvmlibc_errno, 0);

x = valueFromBits(0xc2b00008U);
ASSERT_MPFR_MATCH(mpfr::OP_Exp, x, __llvm_libc::expf(x), tolerance);
EXPECT_EQ(llvmlibc_errno, 0);
}

TEST(ExpfTest, 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;
llvmlibc_errno = 0;
float result = __llvm_libc::expf(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) || llvmlibc_errno != 0)
continue;
ASSERT_MPFR_MATCH(mpfr::OP_Exp, x, __llvm_libc::expf(x), tolerance);
}
}
6 changes: 6 additions & 0 deletions libc/utils/MPFRWrapper/MPFRUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ class MPFRNumber {
case OP_Sin:
mpfr_sin(value, mpfrInput.value, MPFR_RNDN);
break;
case OP_Exp:
mpfr_exp(value, mpfrInput.value, MPFR_RNDN);
break;
case OP_Exp2:
mpfr_exp2(value, mpfrInput.value, MPFR_RNDN);
break;
}
}

Expand Down
6 changes: 1 addition & 5 deletions libc/utils/MPFRWrapper/MPFRUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,7 @@ struct Tolerance {
uint32_t bits;
};

enum Operation {
OP_Abs,
OP_Cos,
OP_Sin,
};
enum Operation { OP_Abs, OP_Cos, OP_Sin, OP_Exp, OP_Exp2 };

namespace internal {

Expand Down