// Copyright 2022 The Tint Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include "src/tint/program_builder.h" #include "src/tint/utils/compiler_macros.h" #include "gtest/gtest.h" using namespace tint::number_suffixes; // NOLINT namespace tint { namespace { // Concats any number of std::vectors template [[nodiscard]] inline auto Concat(Vec&& v1, Vecs&&... vs) { auto total_size = v1.size() + (vs.size() + ...); v1.reserve(total_size); (std::move(vs.begin(), vs.end(), std::back_inserter(v1)), ...); return std::move(v1); } // Next ULP up from kHighestF32 for a float64. constexpr double kHighestF32NextULP = 0x1.fffffe0000001p+127; // Highest subnormal value for a float32. constexpr double kHighestF32Subnormal = 0x0.fffffep-126; // Next ULP up from kHighestF16 for a float64. constexpr double kHighestF16NextULP = 0x1.ffc0000000001p+15; // Highest subnormal value for a float16. constexpr double kHighestF16Subnormal = 0x0.ffcp-14; constexpr double kLowestF32NextULP = -kHighestF32NextULP; constexpr double kLowestF16NextULP = -kHighestF16NextULP; // MSVC (only in release builds) can grumble about some of the inlined numerical overflow / // underflow that's done in this file. We like to think we know what we're doing, so silence the // warning. TINT_BEGIN_DISABLE_WARNING(CONSTANT_OVERFLOW); TEST(NumberTest, Equality) { EXPECT_TRUE(0_a == 0_a); EXPECT_TRUE(10_a == 10_a); EXPECT_TRUE(-10_a == -10_a); EXPECT_TRUE(0_i == 0_i); EXPECT_TRUE(10_i == 10_i); EXPECT_TRUE(-10_i == -10_i); EXPECT_TRUE(0_u == 0_u); EXPECT_TRUE(10_u == 10_u); EXPECT_TRUE(0._a == 0._a); EXPECT_TRUE(-0._a == -0._a); EXPECT_TRUE(10._a == 10._a); EXPECT_TRUE(-10._a == -10._a); EXPECT_TRUE(0_f == 0_f); EXPECT_TRUE(-0_f == -0_f); EXPECT_TRUE(10_f == 10_f); EXPECT_TRUE(-10_f == -10_f); EXPECT_TRUE(0_h == 0_h); EXPECT_TRUE(-0_h == -0_h); EXPECT_TRUE(10_h == 10_h); EXPECT_TRUE(-10_h == -10_h); } TEST(NumberTest, Inequality) { EXPECT_TRUE(0_a != 1_a); EXPECT_TRUE(10_a != 11_a); EXPECT_TRUE(11_a != 10_a); EXPECT_TRUE(-10_a != -11_a); EXPECT_TRUE(-11_a != -10_a); EXPECT_TRUE(0_i != 1_i); EXPECT_TRUE(1_i != 0_i); EXPECT_TRUE(10_i != 11_i); EXPECT_TRUE(11_i != 10_i); EXPECT_TRUE(-10_i != -11_i); EXPECT_TRUE(-11_i != -10_i); EXPECT_TRUE(0_u != 1_u); EXPECT_TRUE(1_u != 0_u); EXPECT_TRUE(10_u != 11_u); EXPECT_TRUE(11_u != 10_u); EXPECT_TRUE(0._a != -0._a); EXPECT_TRUE(-0._a != 0._a); EXPECT_TRUE(10._a != 11._a); EXPECT_TRUE(11._a != 10._a); EXPECT_TRUE(-10._a != -11._a); EXPECT_TRUE(-11._a != -10._a); EXPECT_TRUE(0_f != -0_f); EXPECT_TRUE(-0_f != 0_f); EXPECT_TRUE(-0_f != -1_f); EXPECT_TRUE(-1_f != -0_f); EXPECT_TRUE(10_f != -10_f); EXPECT_TRUE(-10_f != 10_f); EXPECT_TRUE(10_f != 11_f); EXPECT_TRUE(-10_f != -11_f); EXPECT_TRUE(0_h != -0_h); EXPECT_TRUE(-0_h != 0_h); EXPECT_TRUE(-0_h != -1_h); EXPECT_TRUE(-1_h != -0_h); EXPECT_TRUE(10_h != -10_h); EXPECT_TRUE(-10_h != 10_h); EXPECT_TRUE(10_h != 11_h); EXPECT_TRUE(-10_h != -11_h); } TEST(NumberTest, CheckedConvertIdentity) { EXPECT_EQ(CheckedConvert(0_a), 0_a); EXPECT_EQ(CheckedConvert(0_a), 0.0_a); EXPECT_EQ(CheckedConvert(0_i), 0_i); EXPECT_EQ(CheckedConvert(0_u), 0_u); EXPECT_EQ(CheckedConvert(0_f), 0_f); EXPECT_EQ(CheckedConvert(0_h), 0_h); EXPECT_EQ(CheckedConvert(1_a), 1_a); EXPECT_EQ(CheckedConvert(1_a), 1.0_a); EXPECT_EQ(CheckedConvert(1_i), 1_i); EXPECT_EQ(CheckedConvert(1_u), 1_u); EXPECT_EQ(CheckedConvert(1_f), 1_f); EXPECT_EQ(CheckedConvert(1_h), 1_h); } TEST(NumberTest, CheckedConvertLargestValue) { EXPECT_EQ(CheckedConvert(AInt(i32::Highest())), i32::Highest()); EXPECT_EQ(CheckedConvert(AInt(u32::Highest())), u32::Highest()); EXPECT_EQ(CheckedConvert(i32::Highest()), u32(i32::Highest())); EXPECT_EQ(CheckedConvert(AFloat(f32::Highest())), f32::Highest()); EXPECT_EQ(CheckedConvert(AFloat(f16::Highest())), f16::Highest()); } TEST(NumberTest, CheckedConvertLowestValue) { EXPECT_EQ(CheckedConvert(AInt(i32::Lowest())), i32::Lowest()); EXPECT_EQ(CheckedConvert(AInt(u32::Lowest())), u32::Lowest()); EXPECT_EQ(CheckedConvert(AFloat(f32::Lowest())), f32::Lowest()); EXPECT_EQ(CheckedConvert(AFloat(f16::Lowest())), f16::Lowest()); } TEST(NumberTest, CheckedConvertSmallestValue) { EXPECT_EQ(CheckedConvert(AInt(0)), i32(0)); EXPECT_EQ(CheckedConvert(AInt(0)), u32(0)); EXPECT_EQ(CheckedConvert(AFloat(f32::Smallest())), f32::Smallest()); EXPECT_EQ(CheckedConvert(AFloat(f16::Smallest())), f16::Smallest()); } TEST(NumberTest, CheckedConvertExceedsPositiveLimit) { EXPECT_EQ(CheckedConvert(AInt(static_cast(i32::Highest()) + 1)), ConversionFailure::kExceedsPositiveLimit); EXPECT_EQ(CheckedConvert(AInt(static_cast(u32::Highest()) + 1)), ConversionFailure::kExceedsPositiveLimit); EXPECT_EQ(CheckedConvert(u32::Highest()), ConversionFailure::kExceedsPositiveLimit); EXPECT_EQ(CheckedConvert(u32(0x80000000)), ConversionFailure::kExceedsPositiveLimit); EXPECT_EQ(CheckedConvert(f32::Highest()), ConversionFailure::kExceedsPositiveLimit); EXPECT_EQ(CheckedConvert(f32::Highest()), ConversionFailure::kExceedsPositiveLimit); EXPECT_EQ(CheckedConvert(AFloat::Highest()), ConversionFailure::kExceedsPositiveLimit); EXPECT_EQ(CheckedConvert(AFloat::Highest()), ConversionFailure::kExceedsPositiveLimit); EXPECT_EQ(CheckedConvert(AFloat(kHighestF32NextULP)), ConversionFailure::kExceedsPositiveLimit); EXPECT_EQ(CheckedConvert(AFloat(kHighestF16NextULP)), ConversionFailure::kExceedsPositiveLimit); } TEST(NumberTest, CheckedConvertExceedsNegativeLimit) { EXPECT_EQ(CheckedConvert(AInt(static_cast(i32::Lowest()) - 1)), ConversionFailure::kExceedsNegativeLimit); EXPECT_EQ(CheckedConvert(AInt(static_cast(u32::Lowest()) - 1)), ConversionFailure::kExceedsNegativeLimit); EXPECT_EQ(CheckedConvert(i32(-1)), ConversionFailure::kExceedsNegativeLimit); EXPECT_EQ(CheckedConvert(i32::Lowest()), ConversionFailure::kExceedsNegativeLimit); EXPECT_EQ(CheckedConvert(f32::Lowest()), ConversionFailure::kExceedsNegativeLimit); EXPECT_EQ(CheckedConvert(f32::Lowest()), ConversionFailure::kExceedsNegativeLimit); EXPECT_EQ(CheckedConvert(AFloat::Lowest()), ConversionFailure::kExceedsNegativeLimit); EXPECT_EQ(CheckedConvert(AFloat::Lowest()), ConversionFailure::kExceedsNegativeLimit); EXPECT_EQ(CheckedConvert(AFloat(kLowestF32NextULP)), ConversionFailure::kExceedsNegativeLimit); EXPECT_EQ(CheckedConvert(AFloat(kLowestF16NextULP)), ConversionFailure::kExceedsNegativeLimit); } TEST(NumberTest, CheckedConvertSubnormals) { EXPECT_EQ(CheckedConvert(AFloat(kHighestF32Subnormal)), f32(kHighestF32Subnormal)); EXPECT_EQ(CheckedConvert(AFloat(kHighestF16Subnormal)), f16(kHighestF16Subnormal)); EXPECT_EQ(CheckedConvert(AFloat(-kHighestF32Subnormal)), f32(-kHighestF32Subnormal)); EXPECT_EQ(CheckedConvert(AFloat(-kHighestF16Subnormal)), f16(-kHighestF16Subnormal)); } // Test cases for f16 subnormal quantization and BitsRepresentation. // The ULP is based on float rather than double or f16, since F16::Quantize and // F16::BitsRepresentation take float as input. constexpr float lowestPositiveNormalF16 = 0x1p-14; constexpr float lowestPositiveNormalF16PlusULP = 0x1.000002p-14; constexpr float lowestPositiveNormalF16MinusULP = 0x1.fffffep-15; constexpr float highestPositiveSubnormalF16 = 0x0.ffcp-14; constexpr float highestPositiveSubnormalF16PlusULP = 0x1.ff8002p-15; constexpr float highestPositiveSubnormalF16MinusULP = 0x1.ff7ffep-15; constexpr float lowestPositiveSubnormalF16 = 0x1.p-24; constexpr float lowestPositiveSubnormalF16PlusULP = 0x1.000002p-24; constexpr float lowestPositiveSubnormalF16MinusULP = 0x1.fffffep-25; constexpr uint16_t lowestPositiveNormalF16Bits = 0x0400u; constexpr uint16_t highestPositiveSubnormalF16Bits = 0x03ffu; constexpr uint16_t lowestPositiveSubnormalF16Bits = 0x0001u; constexpr float highestNegativeNormalF16 = -lowestPositiveNormalF16; constexpr float highestNegativeNormalF16PlusULP = -lowestPositiveNormalF16MinusULP; constexpr float highestNegativeNormalF16MinusULP = -lowestPositiveNormalF16PlusULP; constexpr float lowestNegativeSubnormalF16 = -highestPositiveSubnormalF16; constexpr float lowestNegativeSubnormalF16PlusULP = -highestPositiveSubnormalF16MinusULP; constexpr float lowestNegativeSubnormalF16MinusULP = -highestPositiveSubnormalF16PlusULP; constexpr float highestNegativeSubnormalF16 = -lowestPositiveSubnormalF16; constexpr float highestNegativeSubnormalF16PlusULP = -lowestPositiveSubnormalF16MinusULP; constexpr float highestNegativeSubnormalF16MinusULP = -lowestPositiveSubnormalF16PlusULP; constexpr uint16_t highestNegativeNormalF16Bits = 0x8400u; constexpr uint16_t lowestNegativeSubnormalF16Bits = 0x83ffu; constexpr uint16_t highestNegativeSubnormalF16Bits = 0x8001u; constexpr float f32_nan = std::numeric_limits::quiet_NaN(); constexpr float f32_inf = std::numeric_limits::infinity(); struct F16TestCase { float input_value; float quantized_value; uint16_t f16_bit_pattern; }; using NumberF16Test = testing::TestWithParam; TEST_P(NumberF16Test, QuantizeF16) { float input_value = GetParam().input_value; float quantized_value = GetParam().quantized_value; std::stringstream ss; ss << "input value = " << input_value << ", expected quantized value = " << quantized_value; SCOPED_TRACE(ss.str()); if (std::isnan(quantized_value)) { EXPECT_TRUE(std::isnan(f16(input_value))); } else { EXPECT_EQ(f16(input_value), quantized_value); } } TEST_P(NumberF16Test, BitsRepresentation) { float input_value = GetParam().input_value; uint16_t representation = GetParam().f16_bit_pattern; std::stringstream ss; ss << "input value = " << input_value << ", expected binary16 bits representation = " << std::hex << std::showbase << representation; SCOPED_TRACE(ss.str()); EXPECT_EQ(f16(input_value).BitsRepresentation(), representation); } TEST_P(NumberF16Test, FromBits) { float input_value = GetParam().quantized_value; uint16_t representation = GetParam().f16_bit_pattern; std::stringstream ss; ss << "binary16 bits representation = " << std::hex << std::showbase << representation << " expected value = " << input_value; SCOPED_TRACE(ss.str()); if (std::isnan(input_value)) { EXPECT_TRUE(std::isnan(f16::FromBits(representation))); } else { EXPECT_EQ(f16::FromBits(representation), f16(input_value)); } } INSTANTIATE_TEST_SUITE_P( NumberF16Test, NumberF16Test, testing::ValuesIn(std::vector{ // NaN, Inf {f32_inf, f32_inf, 0x7c00u}, {-f32_inf, -f32_inf, 0xfc00u}, {f32_nan, f32_nan, 0x7e00u}, {-f32_nan, -f32_nan, 0x7e00u}, // +/- zero {+0.0f, 0.0f, 0x0000u}, {-0.0f, -0.0f, 0x8000u}, // Value in normal f16 range {1.0f, 1.0f, 0x3c00u}, {-1.0f, -1.0f, 0xbc00u}, // 0.00006106496 quantized to 0.000061035156 = 0x1p-14 {0.00006106496f, 0.000061035156f, 0x0400u}, {-0.00006106496f, -0.000061035156f, 0x8400u}, // 1.0004883 quantized to 1.0 = 0x1p0 {1.0004883f, 1.0f, 0x3c00u}, {-1.0004883f, -1.0f, 0xbc00u}, // 8196.0 quantized to 8192.0 = 0x1p13 {8196.0f, 8192.f, 0x7000u}, {-8196.0f, -8192.f, 0xf000u}, // Value in subnormal f16 range {0x0.034p-14f, 0x0.034p-14f, 0x000du}, {-0x0.034p-14f, -0x0.034p-14f, 0x800du}, {0x0.068p-14f, 0x0.068p-14f, 0x001au}, {-0x0.068p-14f, -0x0.068p-14f, 0x801au}, // 0x0.06b7p-14 quantized to 0x0.068p-14 {0x0.06b7p-14f, 0x0.068p-14f, 0x001au}, {-0x0.06b7p-14f, -0x0.068p-14, 0x801au}, // Value out of f16 range {65504.003f, f32_inf, 0x7c00u}, {-65504.003f, -f32_inf, 0xfc00u}, {0x1.234p56f, f32_inf, 0x7c00u}, {-0x4.321p65f, -f32_inf, 0xfc00u}, // Test for subnormal quantization. // Value larger than or equal to lowest positive normal f16 will be quantized to normal f16. {lowestPositiveNormalF16PlusULP, lowestPositiveNormalF16, lowestPositiveNormalF16Bits}, {lowestPositiveNormalF16, lowestPositiveNormalF16, lowestPositiveNormalF16Bits}, // Positive value smaller than lowest positive normal f16 but not smaller than lowest // positive // subnormal f16 will be quantized to subnormal f16 or zero. {lowestPositiveNormalF16MinusULP, highestPositiveSubnormalF16, highestPositiveSubnormalF16Bits}, {highestPositiveSubnormalF16PlusULP, highestPositiveSubnormalF16, highestPositiveSubnormalF16Bits}, {highestPositiveSubnormalF16, highestPositiveSubnormalF16, highestPositiveSubnormalF16Bits}, {highestPositiveSubnormalF16MinusULP, 0x0.ff8p-14, 0x03feu}, {lowestPositiveSubnormalF16PlusULP, lowestPositiveSubnormalF16, lowestPositiveSubnormalF16Bits}, {lowestPositiveSubnormalF16, lowestPositiveSubnormalF16, lowestPositiveSubnormalF16Bits}, // Positive value smaller than lowest positive subnormal f16 will be quantized to zero. {lowestPositiveSubnormalF16MinusULP, 0.0, 0x0000u}, // Test the mantissa discarding, the least significant mantissa bit is 0x1p-24 = // 0x0.004p-14. {0x0.064p-14f, 0x0.064p-14, 0x0019u}, {0x0.067fecp-14f, 0x0.064p-14, 0x0019u}, {0x0.063ffep-14f, 0x0.060p-14, 0x0018u}, {0x0.008p-14f, 0x0.008p-14, 0x0002u}, {0x0.00bffep-14f, 0x0.008p-14, 0x0002u}, {0x0.007ffep-14f, 0x0.004p-14, 0x0001u}, // Vice versa for negative cases. {highestNegativeNormalF16MinusULP, highestNegativeNormalF16, highestNegativeNormalF16Bits}, {highestNegativeNormalF16, highestNegativeNormalF16, highestNegativeNormalF16Bits}, {highestNegativeNormalF16PlusULP, lowestNegativeSubnormalF16, lowestNegativeSubnormalF16Bits}, {lowestNegativeSubnormalF16MinusULP, lowestNegativeSubnormalF16, lowestNegativeSubnormalF16Bits}, {lowestNegativeSubnormalF16, lowestNegativeSubnormalF16, lowestNegativeSubnormalF16Bits}, {lowestNegativeSubnormalF16PlusULP, -0x0.ff8p-14, 0x83feu}, {highestNegativeSubnormalF16MinusULP, highestNegativeSubnormalF16, highestNegativeSubnormalF16Bits}, {highestNegativeSubnormalF16, highestNegativeSubnormalF16, highestNegativeSubnormalF16Bits}, {highestNegativeSubnormalF16PlusULP, -0.0, 0x8000u}, // Test the mantissa discarding. {-0x0.064p-14f, -0x0.064p-14, 0x8019u}, {-0x0.067fecp-14f, -0x0.064p-14, 0x8019u}, {-0x0.063ffep-14f, -0x0.060p-14, 0x8018u}, {-0x0.008p-14f, -0x0.008p-14, 0x8002u}, {-0x0.00bffep-14f, -0x0.008p-14, 0x8002u}, {-0x0.007ffep-14f, -0x0.004p-14, 0x8001u}, ///////////////////////////////////// })); #ifdef OVERFLOW #undef OVERFLOW // corecrt_math.h :( #endif #define OVERFLOW \ {} template auto Overflow = std::optional{}; using BinaryCheckedCase_AInt = std::tuple, AInt, AInt>; using CheckedAddTest_AInt = testing::TestWithParam; using FloatInputTypes = std::variant; using FloatExpectedTypes = std::variant, std::optional, std::optional>; using BinaryCheckedCase_Float = std::tuple; /// Validates that result is equal to expect. If `float_comp` is true, uses EXPECT_FLOAT_EQ to /// compare the values. template void ValidateResult(std::optional result, std::optional expect, bool float_comp = false) { if (!expect) { EXPECT_TRUE(!result) << *result; } else { ASSERT_TRUE(result); if constexpr (IsIntegral) { EXPECT_EQ(*result, *expect); } else { if (float_comp) { EXPECT_FLOAT_EQ(*result, *expect); } else { EXPECT_EQ(*result, *expect); } } } } TEST_P(CheckedAddTest_AInt, Test) { auto expect = std::get<0>(GetParam()); auto a = std::get<1>(GetParam()); auto b = std::get<2>(GetParam()); ValidateResult(CheckedAdd(a, b), expect); ValidateResult(CheckedAdd(b, a), expect); } INSTANTIATE_TEST_SUITE_P( CheckedAddTest_AInt, CheckedAddTest_AInt, testing::ValuesIn(std::vector{ {AInt(0), AInt(0), AInt(0)}, {AInt(1), AInt(1), AInt(0)}, {AInt(2), AInt(1), AInt(1)}, {AInt(0), AInt(-1), AInt(1)}, {AInt(3), AInt(2), AInt(1)}, {AInt(-1), AInt(-2), AInt(1)}, {AInt(0x300), AInt(0x100), AInt(0x200)}, {AInt(0x100), AInt(-0x100), AInt(0x200)}, {AInt::Highest(), AInt(1), AInt(AInt::kHighestValue - 1)}, {AInt::Lowest(), AInt(-1), AInt(AInt::kLowestValue + 1)}, {AInt::Highest(), AInt(0x7fffffff00000000ll), AInt(0x00000000ffffffffll)}, {AInt::Highest(), AInt::Highest(), AInt(0)}, {AInt::Lowest(), AInt::Lowest(), AInt(0)}, {OVERFLOW, AInt(1), AInt::Highest()}, {OVERFLOW, AInt(-1), AInt::Lowest()}, {OVERFLOW, AInt(2), AInt::Highest()}, {OVERFLOW, AInt(-2), AInt::Lowest()}, {OVERFLOW, AInt(10000), AInt::Highest()}, {OVERFLOW, AInt(-10000), AInt::Lowest()}, {OVERFLOW, AInt::Highest(), AInt::Highest()}, {OVERFLOW, AInt::Lowest(), AInt::Lowest()}, //////////////////////////////////////////////////////////////////////// })); using CheckedAddTest_Float = testing::TestWithParam; TEST_P(CheckedAddTest_Float, Test) { auto& p = GetParam(); std::visit( [&](auto&& lhs) { using T = std::decay_t; auto rhs = std::get(std::get<2>(p)); auto expect = std::get>(std::get<0>(p)); EXPECT_TRUE(CheckedAdd(lhs, rhs) == expect) << std::hex << "0x" << lhs << " + 0x" << rhs; EXPECT_TRUE(CheckedAdd(rhs, lhs) == expect) << std::hex << "0x" << lhs << " + 0x" << rhs; }, std::get<1>(p)); } template std::vector CheckedAddTest_FloatCases() { return { {T(0), T(0), T(0)}, {T(1), T(1), T(0)}, {T(2), T(1), T(1)}, {T(0), T(-1), T(1)}, {T(3), T(2), T(1)}, {T(-1), T(-2), T(1)}, {T(0x300), T(0x100), T(0x200)}, {T(0x100), T(-0x100), T(0x200)}, {T::Highest(), T::Highest(), T(0)}, {T::Lowest(), T::Lowest(), T(0)}, {Overflow, T::Highest(), T::Highest()}, {Overflow, T::Lowest(), T::Lowest()}, }; } INSTANTIATE_TEST_SUITE_P(CheckedAddTest_Float, CheckedAddTest_Float, testing::ValuesIn(Concat(CheckedAddTest_FloatCases(), CheckedAddTest_FloatCases(), CheckedAddTest_FloatCases()))); using CheckedSubTest_AInt = testing::TestWithParam; TEST_P(CheckedSubTest_AInt, Test) { auto expect = std::get<0>(GetParam()); auto a = std::get<1>(GetParam()); auto b = std::get<2>(GetParam()); ValidateResult(CheckedSub(a, b), expect); } INSTANTIATE_TEST_SUITE_P( CheckedSubTest_AInt, CheckedSubTest_AInt, testing::ValuesIn(std::vector{ {AInt(0), AInt(0), AInt(0)}, {AInt(1), AInt(1), AInt(0)}, {AInt(0), AInt(1), AInt(1)}, {AInt(-2), AInt(-1), AInt(1)}, {AInt(1), AInt(2), AInt(1)}, {AInt(-3), AInt(-2), AInt(1)}, {AInt(0x100), AInt(0x300), AInt(0x200)}, {AInt(-0x300), AInt(-0x100), AInt(0x200)}, {AInt::Highest(), AInt(AInt::kHighestValue - 1), AInt(-1)}, {AInt::Lowest(), AInt(AInt::kLowestValue + 1), AInt(1)}, {AInt(0x00000000ffffffffll), AInt::Highest(), AInt(0x7fffffff00000000ll)}, {AInt::Highest(), AInt::Highest(), AInt(0)}, {AInt::Lowest(), AInt::Lowest(), AInt(0)}, {OVERFLOW, AInt::Lowest(), AInt(1)}, {OVERFLOW, AInt::Highest(), AInt(-1)}, {OVERFLOW, AInt::Lowest(), AInt(2)}, {OVERFLOW, AInt::Highest(), AInt(-2)}, {OVERFLOW, AInt::Lowest(), AInt(10000)}, {OVERFLOW, AInt::Highest(), AInt(-10000)}, {OVERFLOW, AInt::Lowest(), AInt::Highest()}, //////////////////////////////////////////////////////////////////////// })); using CheckedSubTest_Float = testing::TestWithParam; TEST_P(CheckedSubTest_Float, Test) { auto& p = GetParam(); std::visit( [&](auto&& lhs) { using T = std::decay_t; auto rhs = std::get(std::get<2>(p)); auto expect = std::get>(std::get<0>(p)); ValidateResult(CheckedSub(lhs, rhs), expect); }, std::get<1>(p)); } template std::vector CheckedSubTest_FloatCases() { return { {T(0), T(0), T(0)}, {T(1), T(1), T(0)}, {T(0), T(1), T(1)}, {T(-2), T(-1), T(1)}, {T(1), T(2), T(1)}, {T(-3), T(-2), T(1)}, {T(0x100), T(0x300), T(0x200)}, {T(-0x300), T(-0x100), T(0x200)}, {T::Highest(), T::Highest(), T(0)}, {T::Lowest(), T::Lowest(), T(0)}, {Overflow, T::Lowest(), T::Highest()}, }; } INSTANTIATE_TEST_SUITE_P(CheckedSubTest_Float, CheckedSubTest_Float, testing::ValuesIn(Concat(CheckedSubTest_FloatCases(), CheckedSubTest_FloatCases(), CheckedSubTest_FloatCases()))); using CheckedMulTest_AInt = testing::TestWithParam; TEST_P(CheckedMulTest_AInt, Test) { auto expect = std::get<0>(GetParam()); auto a = std::get<1>(GetParam()); auto b = std::get<2>(GetParam()); ValidateResult(CheckedMul(a, b), expect); ValidateResult(CheckedMul(b, a), expect); } INSTANTIATE_TEST_SUITE_P( CheckedMulTest_AInt, CheckedMulTest_AInt, testing::ValuesIn(std::vector{ {AInt(0), AInt(0), AInt(0)}, {AInt(0), AInt(1), AInt(0)}, {AInt(1), AInt(1), AInt(1)}, {AInt(-1), AInt(-1), AInt(1)}, {AInt(2), AInt(2), AInt(1)}, {AInt(-2), AInt(-2), AInt(1)}, {AInt(0x20000), AInt(0x100), AInt(0x200)}, {AInt(-0x20000), AInt(-0x100), AInt(0x200)}, {AInt(0x4000000000000000ll), AInt(0x80000000ll), AInt(0x80000000ll)}, {AInt(0x4000000000000000ll), AInt(-0x80000000ll), AInt(-0x80000000ll)}, {AInt(0x1000000000000000ll), AInt(0x40000000ll), AInt(0x40000000ll)}, {AInt(-0x1000000000000000ll), AInt(-0x40000000ll), AInt(0x40000000ll)}, {AInt(0x100000000000000ll), AInt(0x1000000), AInt(0x100000000ll)}, {AInt(0x2000000000000000ll), AInt(0x1000000000000000ll), AInt(2)}, {AInt(-0x2000000000000000ll), AInt(0x1000000000000000ll), AInt(-2)}, {AInt(-0x2000000000000000ll), AInt(-0x1000000000000000ll), AInt(2)}, {AInt(-0x2000000000000000ll), AInt(0x1000000000000000ll), AInt(-2)}, {AInt(0x4000000000000000ll), AInt(0x1000000000000000ll), AInt(4)}, {AInt(-0x4000000000000000ll), AInt(0x1000000000000000ll), AInt(-4)}, {AInt(-0x4000000000000000ll), AInt(-0x1000000000000000ll), AInt(4)}, {AInt(-0x4000000000000000ll), AInt(0x1000000000000000ll), AInt(-4)}, {AInt(-0x8000000000000000ll), AInt(0x1000000000000000ll), AInt(-8)}, {AInt(-0x8000000000000000ll), AInt(-0x1000000000000000ll), AInt(8)}, {AInt(0), AInt::Highest(), AInt(0)}, {AInt(0), AInt::Lowest(), AInt(0)}, {OVERFLOW, AInt(0x1000000000000000ll), AInt(8)}, {OVERFLOW, AInt(-0x1000000000000000ll), AInt(-8)}, {OVERFLOW, AInt(0x800000000000000ll), AInt(0x10)}, {OVERFLOW, AInt(0x80000000ll), AInt(0x100000000ll)}, {OVERFLOW, AInt::Highest(), AInt::Highest()}, {OVERFLOW, AInt::Highest(), AInt::Lowest()}, //////////////////////////////////////////////////////////////////////// })); using CheckedMulTest_Float = testing::TestWithParam; TEST_P(CheckedMulTest_Float, Test) { auto& p = GetParam(); std::visit( [&](auto&& lhs) { using T = std::decay_t; auto rhs = std::get(std::get<2>(p)); auto expect = std::get>(std::get<0>(p)); ValidateResult(CheckedMul(lhs, rhs), expect); ValidateResult(CheckedMul(rhs, lhs), expect); }, std::get<1>(p)); } template std::vector CheckedMulTest_FloatCases() { return { {T(0), T(0), T(0)}, {T(0), T(1), T(0)}, {T(1), T(1), T(1)}, {T(-1), T(-1), T(1)}, {T(2), T(2), T(1)}, {T(-2), T(-2), T(1)}, {T(0), T::Highest(), T(0)}, {T(0), T::Lowest(), -T(0)}, {Overflow, T::Highest(), T::Highest()}, {Overflow, T::Lowest(), T::Lowest()}, }; } INSTANTIATE_TEST_SUITE_P(CheckedMulTest_Float, CheckedMulTest_Float, testing::ValuesIn(Concat(CheckedMulTest_FloatCases(), CheckedMulTest_FloatCases(), CheckedMulTest_FloatCases()))); using CheckedDivTest_AInt = testing::TestWithParam; TEST_P(CheckedDivTest_AInt, Test) { auto expect = std::get<0>(GetParam()); auto a = std::get<1>(GetParam()); auto b = std::get<2>(GetParam()); ValidateResult(CheckedDiv(a, b), expect); } INSTANTIATE_TEST_SUITE_P( CheckedDivTest_AInt, CheckedDivTest_AInt, testing::ValuesIn(std::vector{ {AInt(0), AInt(0), AInt(1)}, {AInt(1), AInt(1), AInt(1)}, {AInt(1), AInt(1), AInt(1)}, {AInt(2), AInt(2), AInt(1)}, {AInt(2), AInt(4), AInt(2)}, {AInt::Highest(), AInt::Highest(), AInt(1)}, {AInt::Lowest(), AInt::Lowest(), AInt(1)}, {AInt(1), AInt::Highest(), AInt::Highest()}, {AInt(0), AInt(0), AInt::Highest()}, {AInt(0), AInt(0), AInt::Lowest()}, {OVERFLOW, AInt(123), AInt(0)}, {OVERFLOW, AInt(-123), AInt(0)}, //////////////////////////////////////////////////////////////////////// })); using CheckedDivTest_Float = testing::TestWithParam; TEST_P(CheckedDivTest_Float, Test) { auto& p = GetParam(); std::visit( [&](auto&& lhs) { using T = std::decay_t; auto rhs = std::get(std::get<2>(p)); auto expect = std::get>(std::get<0>(p)); ValidateResult(CheckedDiv(lhs, rhs), expect); }, std::get<1>(p)); } template std::vector CheckedDivTest_FloatCases() { return { {T(0), T(0), T(1)}, {T(1), T(1), T(1)}, {T(1), T(1), T(1)}, {T(2), T(2), T(1)}, {T(2), T(4), T(2)}, {T::Highest(), T::Highest(), T(1)}, {T::Lowest(), T::Lowest(), T(1)}, {T(1), T::Highest(), T::Highest()}, {T(0), T(0), T::Highest()}, {-T(0), T(0), T::Lowest()}, {Overflow, T(123), T(0)}, {Overflow, T(123), T(-0)}, {Overflow, T(-123), T(0)}, {Overflow, T(-123), T(-0)}, }; } INSTANTIATE_TEST_SUITE_P(CheckedDivTest_Float, CheckedDivTest_Float, testing::ValuesIn(Concat(CheckedDivTest_FloatCases(), CheckedDivTest_FloatCases(), CheckedDivTest_FloatCases()))); using CheckedModTest_AInt = testing::TestWithParam; TEST_P(CheckedModTest_AInt, Test) { auto expect = std::get<0>(GetParam()); auto a = std::get<1>(GetParam()); auto b = std::get<2>(GetParam()); EXPECT_TRUE(CheckedMod(a, b) == expect) << std::hex << "0x" << a << " % 0x" << b; } INSTANTIATE_TEST_SUITE_P( CheckedModTest_AInt, CheckedModTest_AInt, testing::ValuesIn(std::vector{ {AInt(0), AInt(0), AInt(1)}, {AInt(0), AInt(1), AInt(1)}, {AInt(1), AInt(10), AInt(3)}, {AInt(2), AInt(10), AInt(4)}, {AInt(0), AInt::Highest(), AInt::Highest()}, {AInt(0), AInt::Lowest(), AInt::Lowest()}, {AInt(2), AInt::Highest(), AInt(5)}, {AInt(1), AInt::Highest(), AInt(6)}, {AInt(0), AInt::Highest(), AInt(7)}, {-AInt{1}, -AInt{10}, AInt{3}}, {-AInt{2}, -AInt{10}, AInt{4}}, {AInt{1}, AInt{10}, -AInt{3}}, {AInt{2}, AInt{10}, -AInt{4}}, {-AInt{1}, -AInt{10}, -AInt{3}}, {-AInt{2}, -AInt{10}, -AInt{4}}, {OVERFLOW, AInt::Highest(), AInt(0)}, {OVERFLOW, AInt::Lowest(), AInt(0)}, //////////////////////////////////////////////////////////////////////// })); using CheckedModTest_Float = testing::TestWithParam; TEST_P(CheckedModTest_Float, Test) { auto& p = GetParam(); std::visit( [&](auto&& lhs) { using T = std::decay_t; auto rhs = std::get(std::get<2>(p)); auto expect = std::get>(std::get<0>(p)); ValidateResult(CheckedMod(lhs, rhs), expect); }, std::get<1>(p)); } template std::vector CheckedModTest_FloatCases() { return { {T(0.5), T(10.5), T(1)}, // {T(0.5), T(10.5), T(2)}, // {T(1.5), T(10.5), T(3)}, // {T(2.5), T(10.5), T(4)}, // {T(0.5), T(10.5), T(5)}, // {T(0), T::Highest(), T::Highest()}, // {T(0), T::Lowest(), T::Lowest()}, // {-T{1}, -T{10}, T{3}}, // {-T{2}, -T{10}, T{4}}, // {T{1}, T{10}, -T{3}}, // {T{2}, T{10}, -T{4}}, // {-T{1}, -T{10}, -T{3}}, // {-T{2}, -T{10}, -T{4}}, // {Overflow, T(123), T(0)}, // {Overflow, T(123), T(-0)}, // {Overflow, T(-123), T(0)}, // {Overflow, T(-123), T(-0)}, }; } INSTANTIATE_TEST_SUITE_P(CheckedModTest_Float, CheckedModTest_Float, testing::ValuesIn(Concat(CheckedModTest_FloatCases(), CheckedModTest_FloatCases(), CheckedModTest_FloatCases()))); using CheckedPowTest_Float = testing::TestWithParam; TEST_P(CheckedPowTest_Float, Test) { auto& p = GetParam(); std::visit( [&](auto&& lhs) { using T = std::decay_t; auto rhs = std::get(std::get<2>(p)); auto expect = std::get>(std::get<0>(p)); ValidateResult(CheckedPow(lhs, rhs), expect, /* float_comp */ true); }, std::get<1>(p)); } template std::vector CheckedPowTest_FloatCases() { return { {T(0), T(0), T(1)}, // {T(0), T(0), T::Highest()}, // {T(1), T(1), T(1)}, // {T(1), T(1), T::Lowest()}, // {T(4), T(2), T(2)}, // {T(8), T(2), T(3)}, // {T(1), T(1), T::Highest()}, // {T(1), T(1), -T(1)}, // {T(0.25), T(2), -T(2)}, // {T(0.125), T(2), -T(3)}, // {T(15.625), T(2.5), T(3)}, // {T(11.313708498), T(2), T(3.5)}, // {T(24.705294220), T(2.5), T(3.5)}, // {T(0.0883883476), T(2), -T(3.5)}, // {Overflow, -T(1), T(1)}, // {Overflow, -T(1), T::Highest()}, // {Overflow, T::Lowest(), T(1)}, // {Overflow, T::Lowest(), T::Highest()}, // {Overflow, T::Lowest(), T::Lowest()}, // {Overflow, T(0), T(0)}, // {Overflow, T(0), -T(1)}, // {Overflow, T(0), T::Lowest()}, // }; } INSTANTIATE_TEST_SUITE_P(CheckedPowTest_Float, CheckedPowTest_Float, testing::ValuesIn(Concat(CheckedPowTest_FloatCases(), CheckedPowTest_FloatCases(), CheckedPowTest_FloatCases()))); using TernaryCheckedCase = std::tuple, AInt, AInt, AInt>; using CheckedMaddTest_AInt = testing::TestWithParam; TEST_P(CheckedMaddTest_AInt, Test) { auto expect = std::get<0>(GetParam()); auto a = std::get<1>(GetParam()); auto b = std::get<2>(GetParam()); auto c = std::get<3>(GetParam()); ValidateResult(CheckedMadd(a, b, c), expect); ValidateResult(CheckedMadd(b, a, c), expect); } INSTANTIATE_TEST_SUITE_P( CheckedMaddTest_AInt, CheckedMaddTest_AInt, testing::ValuesIn(std::vector{ {AInt(0), AInt(0), AInt(0), AInt(0)}, {AInt(0), AInt(1), AInt(0), AInt(0)}, {AInt(1), AInt(1), AInt(1), AInt(0)}, {AInt(2), AInt(1), AInt(1), AInt(1)}, {AInt(0), AInt(1), AInt(-1), AInt(1)}, {AInt(-1), AInt(1), AInt(-2), AInt(1)}, {AInt(-1), AInt(-1), AInt(1), AInt(0)}, {AInt(2), AInt(2), AInt(1), AInt(0)}, {AInt(-2), AInt(-2), AInt(1), AInt(0)}, {AInt(0), AInt::Highest(), AInt(0), AInt(0)}, {AInt(0), AInt::Lowest(), AInt(0), AInt(0)}, {AInt(3), AInt(1), AInt(2), AInt(1)}, {AInt(0x300), AInt(1), AInt(0x100), AInt(0x200)}, {AInt(0x100), AInt(1), AInt(-0x100), AInt(0x200)}, {AInt(0x20000), AInt(0x100), AInt(0x200), AInt(0)}, {AInt(-0x20000), AInt(-0x100), AInt(0x200), AInt(0)}, {AInt(0x4000000000000000ll), AInt(0x80000000ll), AInt(0x80000000ll), AInt(0)}, {AInt(0x4000000000000000ll), AInt(-0x80000000ll), AInt(-0x80000000ll), AInt(0)}, {AInt(0x1000000000000000ll), AInt(0x40000000ll), AInt(0x40000000ll), AInt(0)}, {AInt(-0x1000000000000000ll), AInt(-0x40000000ll), AInt(0x40000000ll), AInt(0)}, {AInt(0x100000000000000ll), AInt(0x1000000), AInt(0x100000000ll), AInt(0)}, {AInt(0x2000000000000000ll), AInt(0x1000000000000000ll), AInt(2), AInt(0)}, {AInt(-0x2000000000000000ll), AInt(0x1000000000000000ll), AInt(-2), AInt(0)}, {AInt(-0x2000000000000000ll), AInt(-0x1000000000000000ll), AInt(2), AInt(0)}, {AInt(-0x2000000000000000ll), AInt(0x1000000000000000ll), AInt(-2), AInt(0)}, {AInt(0x4000000000000000ll), AInt(0x1000000000000000ll), AInt(4), AInt(0)}, {AInt(-0x4000000000000000ll), AInt(0x1000000000000000ll), AInt(-4), AInt(0)}, {AInt(-0x4000000000000000ll), AInt(-0x1000000000000000ll), AInt(4), AInt(0)}, {AInt(-0x4000000000000000ll), AInt(0x1000000000000000ll), AInt(-4), AInt(0)}, {AInt(-0x8000000000000000ll), AInt(0x1000000000000000ll), AInt(-8), AInt(0)}, {AInt(-0x8000000000000000ll), AInt(-0x1000000000000000ll), AInt(8), AInt(0)}, {AInt::Highest(), AInt(1), AInt(1), AInt(AInt::kHighestValue - 1)}, {AInt::Lowest(), AInt(1), AInt(-1), AInt(AInt::kLowestValue + 1)}, {AInt::Highest(), AInt(1), AInt(0x7fffffff00000000ll), AInt(0x00000000ffffffffll)}, {AInt::Highest(), AInt(1), AInt::Highest(), AInt(0)}, {AInt::Lowest(), AInt(1), AInt::Lowest(), AInt(0)}, {OVERFLOW, AInt(0x1000000000000000ll), AInt(8), AInt(0)}, {OVERFLOW, AInt(-0x1000000000000000ll), AInt(-8), AInt(0)}, {OVERFLOW, AInt(0x800000000000000ll), AInt(0x10), AInt(0)}, {OVERFLOW, AInt(0x80000000ll), AInt(0x100000000ll), AInt(0)}, {OVERFLOW, AInt::Highest(), AInt::Highest(), AInt(0)}, {OVERFLOW, AInt::Highest(), AInt::Lowest(), AInt(0)}, {OVERFLOW, AInt(1), AInt(1), AInt::Highest()}, {OVERFLOW, AInt(1), AInt(-1), AInt::Lowest()}, {OVERFLOW, AInt(1), AInt(2), AInt::Highest()}, {OVERFLOW, AInt(1), AInt(-2), AInt::Lowest()}, {OVERFLOW, AInt(1), AInt(10000), AInt::Highest()}, {OVERFLOW, AInt(1), AInt(-10000), AInt::Lowest()}, {OVERFLOW, AInt(1), AInt::Highest(), AInt::Highest()}, {OVERFLOW, AInt(1), AInt::Lowest(), AInt::Lowest()}, {OVERFLOW, AInt(1), AInt::Highest(), AInt(1)}, {OVERFLOW, AInt(1), AInt::Lowest(), AInt(-1)}, })); TINT_END_DISABLE_WARNING(CONSTANT_OVERFLOW); } // namespace } // namespace tint