120 changes: 120 additions & 0 deletions libc/test/src/math/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,126 @@ add_fp_unittest(
libc.utils.FPUtil.fputil
)

add_fp_unittest(
lround_test
NEED_MPFR
SUITE
libc_math_unittests
SRCS
lround_test.cpp
HDRS
RoundToIntegerTest.h
DEPENDS
libc.include.errno
libc.include.math
libc.src.errno.__errno_location
libc.src.fenv.feclearexcept
libc.src.fenv.feraiseexcept
libc.src.fenv.fetestexcept
libc.src.math.lround
libc.utils.FPUtil.fputil
)

add_fp_unittest(
lroundf_test
NEED_MPFR
SUITE
libc_math_unittests
SRCS
lroundf_test.cpp
HDRS
RoundToIntegerTest.h
DEPENDS
libc.include.errno
libc.include.math
libc.src.errno.__errno_location
libc.src.fenv.feclearexcept
libc.src.fenv.feraiseexcept
libc.src.fenv.fetestexcept
libc.src.math.lroundf
libc.utils.FPUtil.fputil
)

add_fp_unittest(
lroundl_test
NEED_MPFR
SUITE
libc_math_unittests
SRCS
lroundl_test.cpp
HDRS
RoundToIntegerTest.h
DEPENDS
libc.include.errno
libc.include.math
libc.src.errno.__errno_location
libc.src.fenv.feclearexcept
libc.src.fenv.feraiseexcept
libc.src.fenv.fetestexcept
libc.src.math.lroundl
libc.utils.FPUtil.fputil
)

add_fp_unittest(
llround_test
NEED_MPFR
SUITE
libc_math_unittests
SRCS
llround_test.cpp
HDRS
RoundToIntegerTest.h
DEPENDS
libc.include.errno
libc.include.math
libc.src.errno.__errno_location
libc.src.fenv.feclearexcept
libc.src.fenv.feraiseexcept
libc.src.fenv.fetestexcept
libc.src.math.llround
libc.utils.FPUtil.fputil
)

add_fp_unittest(
llroundf_test
NEED_MPFR
SUITE
libc_math_unittests
SRCS
llroundf_test.cpp
HDRS
RoundToIntegerTest.h
DEPENDS
libc.include.errno
libc.include.math
libc.src.errno.__errno_location
libc.src.fenv.feclearexcept
libc.src.fenv.feraiseexcept
libc.src.fenv.fetestexcept
libc.src.math.llroundf
libc.utils.FPUtil.fputil
)

add_fp_unittest(
llroundl_test
NEED_MPFR
SUITE
libc_math_unittests
SRCS
llroundl_test.cpp
HDRS
RoundToIntegerTest.h
DEPENDS
libc.include.errno
libc.include.math
libc.src.errno.__errno_location
libc.src.fenv.feclearexcept
libc.src.fenv.feraiseexcept
libc.src.fenv.fetestexcept
libc.src.math.llroundl
libc.utils.FPUtil.fputil
)

add_fp_unittest(
expf_test
NEED_MPFR
Expand Down
217 changes: 217 additions & 0 deletions libc/test/src/math/RoundToIntegerTest.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
//===-- Utility class to test different flavors of [l|ll]round --*- 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_TEST_SRC_MATH_ROUNDTOINTEGERTEST_H
#define LLVM_LIBC_TEST_SRC_MATH_ROUNDTOINTEGERTEST_H

#include "src/errno/llvmlibc_errno.h"
#include "src/fenv/feclearexcept.h"
#include "src/fenv/feraiseexcept.h"
#include "src/fenv/fetestexcept.h"
#include "utils/CPP/TypeTraits.h"
#include "utils/FPUtil/FPBits.h"
#include "utils/MPFRWrapper/MPFRUtils.h"
#include "utils/UnitTest/Test.h"

#include <math.h>
#if math_errhandling & MATH_ERRNO
#include <errno.h>
#endif
#if math_errhandling & MATH_ERREXCEPT
#include "utils/FPUtil/FEnv.h"
#endif

namespace mpfr = __llvm_libc::testing::mpfr;

template <typename F, typename I>
class RoundToIntegerTestTemplate : public __llvm_libc::testing::Test {
public:
typedef I (*RoundToIntegerFunc)(F);

private:
using FPBits = __llvm_libc::fputil::FPBits<F>;
using UIntType = typename FPBits::UIntType;

const F zero = __llvm_libc::fputil::FPBits<F>::zero();
const F negZero = __llvm_libc::fputil::FPBits<F>::negZero();
const F inf = __llvm_libc::fputil::FPBits<F>::inf();
const F negInf = __llvm_libc::fputil::FPBits<F>::negInf();
const F nan = __llvm_libc::fputil::FPBits<F>::buildNaN(1);
static constexpr I IntegerMin = I(1) << (sizeof(I) * 8 - 1);
static constexpr I IntegerMax = -(IntegerMin + 1);

void testOneInput(RoundToIntegerFunc func, F input, I expected,
bool expectError) {
#if math_errhandling & MATH_ERRNO
llvmlibc_errno = 0;
#endif
#if math_errhandling & MATH_ERREXCEPT
__llvm_libc::feclearexcept(FE_ALL_EXCEPT);
#endif

ASSERT_EQ(func(input), expected);

if (expectError) {
#if math_errhandling & MATH_ERREXCEPT
ASSERT_EQ(__llvm_libc::fetestexcept(FE_ALL_EXCEPT), FE_INVALID);
#endif
#if math_errhandling & MATH_ERRNO
ASSERT_EQ(llvmlibc_errno, EDOM);
#endif
} else {
#if math_errhandling & MATH_ERREXCEPT
ASSERT_EQ(__llvm_libc::fetestexcept(FE_ALL_EXCEPT), 0);
#endif
#if math_errhandling & MATH_ERRNO
ASSERT_EQ(llvmlibc_errno, 0);
#endif
}
}

public:
void SetUp() override {
#if math_errhandling & MATH_ERREXCEPT
// We will disable all exceptions so that the test will not
// crash with SIGFPE. We can still use fetestexcept to check
// if the appropriate flag was raised.
__llvm_libc::fputil::disableExcept(FE_ALL_EXCEPT);
#endif
}

void testInfinityAndNaN(RoundToIntegerFunc func) {
testOneInput(func, inf, IntegerMax, true);
testOneInput(func, negInf, IntegerMin, true);
testOneInput(func, nan, IntegerMax, true);
}

void testRoundNumbers(RoundToIntegerFunc func) {
testOneInput(func, zero, I(0), false);
testOneInput(func, negZero, I(0), false);
testOneInput(func, F(1.0), I(1), false);
testOneInput(func, F(-1.0), I(-1), false);
testOneInput(func, F(10.0), I(10), false);
testOneInput(func, F(-10.0), I(-10), false);
testOneInput(func, F(1234.0), I(1234), false);
testOneInput(func, F(-1234.0), I(-1234), false);

// The rest of this this function compares with an equivalent MPFR function
// which rounds floating point numbers to long values. There is no MPFR
// function to round to long long or wider integer values. So, we will
// the remaining tests only if the width of I less than equal to that of
// long.
if (sizeof(I) > sizeof(long))
return;

constexpr int exponentLimit = sizeof(I) * 8 - 1;
// We start with 1.0 so that the implicit bit for x86 long doubles
// is set.
FPBits bits(F(1.0));
bits.exponent = exponentLimit + FPBits::exponentBias;
bits.sign = 1;
bits.mantissa = 0;

F x = bits;
long mpfrResult;
bool erangeflag = mpfr::RoundToLong(x, mpfrResult);
ASSERT_FALSE(erangeflag);
testOneInput(func, x, mpfrResult, false);
}

void testFractions(RoundToIntegerFunc func) {
testOneInput(func, F(0.5), I(1), false);
testOneInput(func, F(-0.5), I(-1), false);
testOneInput(func, F(0.115), I(0), false);
testOneInput(func, F(-0.115), I(0), false);
testOneInput(func, F(0.715), I(1), false);
testOneInput(func, F(-0.715), I(-1), false);
}

void testIntegerOverflow(RoundToIntegerFunc func) {
// This function compares with an equivalent MPFR function which rounds
// floating point numbers to long values. There is no MPFR function to
// round to long long or wider integer values. So, we will peform the
// comparisons in this function only if the width of I less than equal to
// that of long.
if (sizeof(I) > sizeof(long))
return;

constexpr int exponentLimit = sizeof(I) * 8 - 1;
// We start with 1.0 so that the implicit bit for x86 long doubles
// is set.
FPBits bits(F(1.0));
bits.exponent = exponentLimit + FPBits::exponentBias;
bits.sign = 1;
bits.mantissa = UIntType(0x1)
<< (__llvm_libc::fputil::MantissaWidth<F>::value - 1);

F x = bits;
long mpfrResult;
bool erangeflag = mpfr::RoundToLong(x, mpfrResult);
ASSERT_TRUE(erangeflag);
testOneInput(func, x, IntegerMin, true);
}

void testSubnormalRange(RoundToIntegerFunc func) {
// This function compares with an equivalent MPFR function which rounds
// floating point numbers to long values. There is no MPFR function to
// round to long long or wider integer values. So, we will peform the
// comparisons in this function only if the width of I less than equal to
// that of long.
if (sizeof(I) > sizeof(long))
return;

constexpr UIntType count = 1000001;
constexpr UIntType step =
(FPBits::maxSubnormal - FPBits::minSubnormal) / count;
for (UIntType i = FPBits::minSubnormal; i <= FPBits::maxSubnormal;
i += step) {
F x = FPBits(i);
// All subnormal numbers should round to zero.
testOneInput(func, x, 0L, false);
}
}

void testNormalRange(RoundToIntegerFunc func) {
// This function compares with an equivalent MPFR function which rounds
// floating point numbers to long values. There is no MPFR function to
// round to long long or wider integer values. So, we will peform the
// comparisons in this function only if the width of I less than equal to
// that of long.
if (sizeof(I) > sizeof(long))
return;

constexpr UIntType count = 1000001;
constexpr UIntType step = (FPBits::maxNormal - FPBits::minNormal) / count;
for (UIntType i = FPBits::minNormal; i <= FPBits::maxNormal; i += step) {
F x = FPBits(i);
// In normal range on x86 platforms, the long double implicit 1 bit can be
// zero making the numbers NaN. We will skip them.
if (isnan(x)) {
continue;
}

long mpfrResult;
bool erangeflag = mpfr::RoundToLong(x, mpfrResult);
if (erangeflag)
testOneInput(func, x, x > 0 ? IntegerMax : IntegerMin, true);
else
testOneInput(func, x, mpfrResult, false);
}
}
};

#define LIST_ROUND_TO_INTEGER_TESTS(F, I, func) \
using RoundToIntegerTest = RoundToIntegerTestTemplate<F, I>; \
TEST_F(RoundToIntegerTest, InfinityAndNaN) { testInfinityAndNaN(&func); } \
TEST_F(RoundToIntegerTest, RoundNumbers) { testRoundNumbers(&func); } \
TEST_F(RoundToIntegerTest, Fractions) { testFractions(&func); } \
TEST_F(RoundToIntegerTest, IntegerOverflow) { testIntegerOverflow(&func); } \
TEST_F(RoundToIntegerTest, SubnormalRange) { testSubnormalRange(&func); } \
TEST_F(RoundToIntegerTest, NormalRange) { testNormalRange(&func); }

#endif // LLVM_LIBC_TEST_SRC_MATH_ROUNDTOINTEGERTEST_H
14 changes: 14 additions & 0 deletions libc/test/src/math/llround_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//===-- Unittests for llround ---------------------------------------------===//
//
// 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 "RoundToIntegerTest.h"

#include "include/math.h"
#include "src/math/llround.h"

LIST_ROUND_TO_INTEGER_TESTS(double, long long, __llvm_libc::llround)
14 changes: 14 additions & 0 deletions libc/test/src/math/llroundf_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//===-- Unittests for llroundf --------------------------------------------===//
//
// 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 "RoundToIntegerTest.h"

#include "include/math.h"
#include "src/math/llroundf.h"

LIST_ROUND_TO_INTEGER_TESTS(float, long long, __llvm_libc::llroundf)
14 changes: 14 additions & 0 deletions libc/test/src/math/llroundl_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//===-- Unittests for llroundl --------------------------------------------===//
//
// 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 "RoundToIntegerTest.h"

#include "include/math.h"
#include "src/math/llroundl.h"

LIST_ROUND_TO_INTEGER_TESTS(long double, long long, __llvm_libc::llroundl)
14 changes: 14 additions & 0 deletions libc/test/src/math/lround_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//===-- Unittests for lround ----------------------------------------------===//
//
// 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 "RoundToIntegerTest.h"

#include "include/math.h"
#include "src/math/lround.h"

LIST_ROUND_TO_INTEGER_TESTS(double, long, __llvm_libc::lround)
14 changes: 14 additions & 0 deletions libc/test/src/math/lroundf_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//===-- Unittests for lroundf ---------------------------------------------===//
//
// 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 "RoundToIntegerTest.h"

#include "include/math.h"
#include "src/math/lroundf.h"

LIST_ROUND_TO_INTEGER_TESTS(float, long, __llvm_libc::lroundf)
14 changes: 14 additions & 0 deletions libc/test/src/math/lroundl_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//===-- Unittests for lroundl ---------------------------------------------===//
//
// 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 "RoundToIntegerTest.h"

#include "include/math.h"
#include "src/math/lroundl.h"

LIST_ROUND_TO_INTEGER_TESTS(long double, long, __llvm_libc::lroundl)
3 changes: 3 additions & 0 deletions libc/utils/FPUtil/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ add_header_library(
NormalFloat.h
DEPENDS
libc.include.math
libc.include.errno
libc.include.fenv
libc.src.errno.__errno_location
libc.utils.CPP.standalone_cpp
)

Expand Down
56 changes: 56 additions & 0 deletions libc/utils/FPUtil/NearestIntegerOperations.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@

#include "utils/CPP/TypeTraits.h"

#include <math.h>
#if math_errhandling & MATH_ERREXCEPT
#include "FEnv.h"
#endif
#if math_errhandling & MATH_ERRNO
#include "src/errno/llvmlibc_errno.h"
#include <errno.h>
#endif

namespace __llvm_libc {
namespace fputil {

Expand Down Expand Up @@ -150,6 +159,53 @@ static inline T round(T x) {
}
}

template <typename F, typename I,
cpp::EnableIfType<cpp::IsFloatingPointType<F>::Value &&
cpp::IsIntegral<I>::Value,
int> = 0>
static inline I roundToSignedInteger(F x) {
constexpr I IntegerMin = (I(1) << (sizeof(I) * 8 - 1));
constexpr I IntegerMax = -(IntegerMin + 1);

using FPBits = FPBits<F>;
F roundedValue = round(x);
FPBits bits(roundedValue);
auto setDomainErrorAndRaiseInvalid = []() {
#if math_errhandling & MATH_ERRNO
llvmlibc_errno = EDOM;
#endif
#if math_errhandling & MATH_ERREXCEPT
raiseExcept(FE_INVALID);
#endif
};

if (bits.isInfOrNaN()) {
// Result of round is infinity or NaN only if x is infinity
// or NaN.
setDomainErrorAndRaiseInvalid();
return bits.sign ? IntegerMin : IntegerMax;
}

int exponent = bits.getExponent();
constexpr int exponentLimit = sizeof(I) * 8 - 1;
if (exponent > exponentLimit) {
setDomainErrorAndRaiseInvalid();
return bits.sign ? IntegerMin : IntegerMax;
} else if (exponent == exponentLimit) {
if (bits.sign == 0 || bits.mantissa != 0) {
setDomainErrorAndRaiseInvalid();
return bits.sign ? IntegerMin : IntegerMax;
}
// If the control reaches here, then it means that the rounded
// value is the most negative number for the signed integer type I.
}

// For all other cases, if |roundedValue| can fit in the integer type |I|,
// we just return the roundedValue. Implicit conversion will convert the
// floating point value to the exact integer value.
return roundedValue;
}

} // namespace fputil
} // namespace __llvm_libc

Expand Down
19 changes: 19 additions & 0 deletions libc/utils/MPFRWrapper/MPFRUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,16 @@ class MPFRNumber {
return result;
}

bool roundToLong(long &result) const {
// We first calculate the rounded value. This way, when converting
// to long using mpfr_get_si, the rounding direction of MPFR_RNDN
// (or any other rounding mode), does not have an influence.
MPFRNumber roundedValue = round();
mpfr_clear_erangeflag();
result = mpfr_get_si(roundedValue.value, MPFR_RNDN);
return mpfr_erangeflag_p();
}

MPFRNumber sin() const {
MPFRNumber result;
mpfr_sin(result.value, value, MPFR_RNDN);
Expand Down Expand Up @@ -555,6 +565,15 @@ template bool compareBinaryOperationOneOutput<long double>(

} // namespace internal

template <typename T> bool RoundToLong(T x, long &result) {
MPFRNumber mpfr(x);
return mpfr.roundToLong(result);
}

template bool RoundToLong<float>(float, long &);
template bool RoundToLong<double>(double, long &);
template bool RoundToLong<long double>(long double, long &);

} // namespace mpfr
} // namespace testing
} // namespace __llvm_libc
2 changes: 2 additions & 0 deletions libc/utils/MPFRWrapper/MPFRUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ getMPFRMatcher(InputType input, OutputType outputUnused, double t) {
return internal::MPFRMatcher<op, InputType, OutputType>(input, t);
}

template <typename T> bool RoundToLong(T x, long &result);

} // namespace mpfr
} // namespace testing
} // namespace __llvm_libc
Expand Down