blob: 4ff14c41b621d0fe811fe5b23ff9ff700309a16a [file] [edit]
//===-- Utility class to test fxdivi functions ------------------*- 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
//
//===----------------------------------------------------------------------===//
#include "src/__support/CPP/type_traits/conditional.h"
#include "test/UnitTest/Test.h"
#include "hdr/signal_macros.h"
#include "src/__support/CPP/limits.h"
#include "src/__support/fixed_point/fx_rep.h"
namespace cpp = LIBC_NAMESPACE::cpp;
template <typename FXType, typename IntType>
class FxDiviTest : public LIBC_NAMESPACE::testing::Test {
using FXRep = LIBC_NAMESPACE::fixed_point::FXRep<FXType>;
static constexpr FXType fx_max = FXRep::MAX();
static constexpr FXType fx_min = FXRep::MIN();
static constexpr FXType fx_zero = FXRep::ZERO();
static constexpr FXType epsilon = FXRep::EPS();
static constexpr FXType one_half = FXRep::ONE_HALF();
static constexpr FXType one_fourth = FXRep::ONE_FOURTH();
static constexpr FXType one_eighth = FXRep::ONE_EIGHTH();
static constexpr bool is_signed = (FXRep::SIGN_LEN > 0);
static constexpr bool has_integral = (FXRep::INTEGRAL_LEN > 0);
static constexpr int F = FXRep::FRACTION_LEN;
static constexpr auto abs_diff = [](FXType a, FXType b) {
return (a > b) ? (a - b) : (b - a);
};
using CompType =
cpp::conditional_t<is_signed, long accum, unsigned long accum>;
// Here, expected() uses CompType's own division operator as a reference which
// has its own independent error bounds (division operator on CompType can
// have almost 2 ulp of error as per ISO/IEC TR 18037:2008(E),
// clause 4.1.6.2.1). The fxdivi implementation being tested here has an exact
// fast path when the denominator is a power of 2, so do not use this helper
// for such denominators. Instead, use the actual expected fixed-point
// literal.
static constexpr auto expected = [](IntType n, IntType d) -> FXType {
return static_cast<FXType>(static_cast<CompType>(n) /
static_cast<CompType>(d));
};
public:
typedef FXType (*FxDiviFunc)(IntType, IntType);
void testBasicNumbers(FxDiviFunc func) {
EXPECT_TRUE(abs_diff(func(1, 3), expected(1, 3)) <= epsilon);
EXPECT_TRUE(abs_diff(func(2, 3), expected(2, 3)) <= epsilon);
EXPECT_EQ(func(3, 4), 3 * one_fourth);
EXPECT_TRUE(abs_diff(func(5, 7), expected(5, 7)) <= epsilon);
if constexpr (is_signed) {
EXPECT_TRUE(abs_diff(func(-5, 7), expected(-5, 7)) <= epsilon);
}
EXPECT_TRUE(abs_diff(func(1043, 2764), expected(1043, 2764)) <= epsilon);
EXPECT_TRUE(abs_diff(func(60000, 720293), expected(60000, 720293)) <=
epsilon);
EXPECT_EQ(func(128, 256), one_half);
EXPECT_EQ(func(1, 2), one_half);
EXPECT_EQ(func(1, 4), one_fourth);
EXPECT_EQ(func(1, 8), one_eighth);
EXPECT_EQ(func(1, 16), static_cast<FXType>(0.0625));
if constexpr (is_signed) {
EXPECT_EQ(func(-1, 2), -one_half);
EXPECT_EQ(func(1, -4), -one_fourth);
EXPECT_EQ(func(-1, 8), -one_eighth);
EXPECT_EQ(func(1, -16), static_cast<FXType>(-0.0625));
}
if constexpr (has_integral) {
EXPECT_TRUE(abs_diff(func(27, 23), expected(27, 23)) <= epsilon);
}
}
void testEdgeCases(FxDiviFunc func) {
constexpr int int_digits = cpp::numeric_limits<IntType>::digits;
constexpr IntType int_max = cpp::numeric_limits<IntType>::max();
EXPECT_EQ(func(0, 10), fx_zero);
if constexpr (is_signed) {
EXPECT_EQ(func(0, -10), fx_zero);
}
if constexpr (is_signed && (F < int_digits)) {
constexpr IntType edge = static_cast<IntType>(1) << F;
EXPECT_EQ(func(-edge, edge), static_cast<FXType>(-1));
if constexpr (has_integral) {
EXPECT_EQ(func(edge - 1, edge), static_cast<FXType>(1) - epsilon);
} else {
EXPECT_EQ(func(edge - 1, edge), fx_max);
}
}
if constexpr (has_integral) {
EXPECT_EQ(func(int_max, int_max), static_cast<FXType>(1));
EXPECT_TRUE(abs_diff(func(int_max - 1, int_max),
static_cast<FXType>(1) - epsilon) <= epsilon);
// Integer extrema need not overflow the fixed-point result type.
if constexpr (int_digits <= FXRep::INTEGRAL_LEN)
EXPECT_EQ(func(int_max, 1), static_cast<FXType>(int_max));
else
EXPECT_EQ(func(int_max, 1), fx_max);
} else {
EXPECT_EQ(func(int_max, int_max), fx_max);
// When the integer and fraction precisions match, this quotient can
// round to one ulp below fx_max.
if constexpr (int_digits == F)
EXPECT_TRUE(abs_diff(func(int_max - 1, int_max), fx_max) <= epsilon);
else
EXPECT_EQ(func(int_max - 1, int_max), fx_max);
EXPECT_EQ(func(27, 23), fx_max);
}
// Rounding direction for magnitudes smaller than the representable
// precision is implementation defined. The result must be within 1 ulp.
EXPECT_TRUE(abs_diff(func(1, int_max), fx_zero) <= epsilon);
if constexpr (is_signed) {
constexpr IntType int_min = cpp::numeric_limits<IntType>::min();
if constexpr (has_integral) {
EXPECT_TRUE(abs_diff(func(int_min, int_max), static_cast<FXType>(-1)) <=
epsilon);
EXPECT_EQ(func(int_min, int_min), static_cast<FXType>(1));
} else {
EXPECT_EQ(func(int_min, int_max), fx_min);
EXPECT_EQ(func(int_min, int_min), fx_max);
}
if constexpr (int_digits <= FXRep::INTEGRAL_LEN) {
EXPECT_EQ(func(int_min, 1), static_cast<FXType>(int_min));
EXPECT_EQ(func(int_max, -1), -static_cast<FXType>(int_max));
} else {
EXPECT_EQ(func(int_min, 1), fx_min);
EXPECT_EQ(func(int_max, -1), fx_min);
}
// Cannot EXPECT_EQ even though int_min is a power of 2 because rounding
// direction for magnitudes smaller than the representable precision is
// implementation defined. The result must be within 1 ulp.
EXPECT_TRUE(abs_diff(func(1, int_min), fx_zero) <= epsilon);
// The positive magnitude of int_min needs one more integral bit than
// int_max. Convert before negating to avoid integer overflow.
if constexpr (int_digits < FXRep::INTEGRAL_LEN)
EXPECT_EQ(func(int_min, -1), -static_cast<FXType>(int_min));
else
EXPECT_EQ(func(int_min, -1), fx_max);
}
if constexpr (has_integral) {
EXPECT_EQ(func(1, 1), static_cast<FXType>(1));
EXPECT_EQ(func(2, 1), static_cast<FXType>(2));
EXPECT_EQ(func(3, 1), static_cast<FXType>(3));
} else {
EXPECT_EQ(func(1, 1), fx_max);
EXPECT_EQ(func(2, 1), fx_max);
EXPECT_EQ(func(3, 1), fx_max);
}
if constexpr (is_signed) {
EXPECT_EQ(func(-1, 1), static_cast<FXType>(-1));
EXPECT_EQ(func(1, -1), static_cast<FXType>(-1));
if constexpr (has_integral) {
EXPECT_EQ(func(-1, -1), static_cast<FXType>(1));
EXPECT_EQ(func(3, -1), static_cast<FXType>(-3));
EXPECT_EQ(func(-3, -1), static_cast<FXType>(3));
EXPECT_EQ(func(-3, 1), static_cast<FXType>(-3));
} else {
EXPECT_EQ(func(-1, -1), fx_max);
EXPECT_EQ(func(3, -1), fx_min);
EXPECT_EQ(func(-3, -1), fx_max);
}
}
// Construct saturation operands only when they fit in IntType.
if constexpr (has_integral && FXRep::INTEGRAL_LEN < int_digits) {
constexpr IntType at_max = static_cast<IntType>(1) << FXRep::INTEGRAL_LEN;
EXPECT_EQ(func(at_max, 1), fx_max);
if constexpr (at_max <= int_max / 6) {
constexpr IntType over_max = static_cast<IntType>(6) * at_max;
EXPECT_EQ(func(over_max, 3), fx_max);
}
}
}
void testWideOperands(FxDiviFunc func) {
if constexpr (sizeof(IntType) * 8 > 32) {
constexpr IntType big_pow2 = static_cast<IntType>(1) << 40;
if constexpr (has_integral) {
EXPECT_EQ(func(big_pow2, big_pow2), static_cast<FXType>(1));
} else {
EXPECT_EQ(func(big_pow2, big_pow2), fx_max);
}
EXPECT_EQ(func(big_pow2, big_pow2 << 1), one_half);
if constexpr (is_signed) {
EXPECT_EQ(func(-big_pow2, big_pow2), static_cast<FXType>(-1));
}
constexpr IntType big_non_pow2 = big_pow2 + 7;
EXPECT_TRUE(abs_diff(func(3, big_non_pow2), fx_zero) <= epsilon);
EXPECT_EQ(func(big_non_pow2, big_non_pow2 << 1), one_half);
}
}
void testInvalidNumbers([[maybe_unused]] FxDiviFunc func) {
EXPECT_DEATH([func] { func(1, 0); }, WITH_SIGNAL(-1));
if constexpr (is_signed) {
EXPECT_DEATH([func] { func(-1, 0); }, WITH_SIGNAL(-1));
}
}
};
#if defined(LIBC_ADD_NULL_CHECKS)
#define LIST_FXDIVI_TESTS(Name, FXType, IntType, func) \
using LlvmLibc##Name##Divi##Test = FxDiviTest<FXType, IntType>; \
TEST_F(LlvmLibc##Name##Divi##Test, InvalidNumbers) { \
testInvalidNumbers(&func); \
} \
TEST_F(LlvmLibc##Name##Divi##Test, BasicNumbers) { \
testBasicNumbers(&func); \
} \
TEST_F(LlvmLibc##Name##Divi##Test, EdgeCases) { testEdgeCases(&func); } \
TEST_F(LlvmLibc##Name##Divi##Test, WideOperands) { \
testWideOperands(&func); \
} \
static_assert(true, "Require semicolon.")
#else
#define LIST_FXDIVI_TESTS(Name, FXType, IntType, func) \
using LlvmLibc##Name##Divi##Test = FxDiviTest<FXType, IntType>; \
TEST_F(LlvmLibc##Name##Divi##Test, BasicNumbers) { \
testBasicNumbers(&func); \
} \
TEST_F(LlvmLibc##Name##Divi##Test, EdgeCases) { testEdgeCases(&func); } \
TEST_F(LlvmLibc##Name##Divi##Test, WideOperands) { \
testWideOperands(&func); \
} \
static_assert(true, "Require semicolon.")
#endif // LIBC_ADD_NULL_CHECKS