blob: fe036631b2326743f855caba494331da08a4e028 [file] [log] [blame]
// 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 <cmath>
#include <tuple>
#include <variant>
#include <vector>
#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 <typename Vec, typename... Vecs>
[[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<AInt>(0_a), 0_a);
EXPECT_EQ(CheckedConvert<AFloat>(0_a), 0.0_a);
EXPECT_EQ(CheckedConvert<i32>(0_i), 0_i);
EXPECT_EQ(CheckedConvert<u32>(0_u), 0_u);
EXPECT_EQ(CheckedConvert<f32>(0_f), 0_f);
EXPECT_EQ(CheckedConvert<f16>(0_h), 0_h);
EXPECT_EQ(CheckedConvert<AInt>(1_a), 1_a);
EXPECT_EQ(CheckedConvert<AFloat>(1_a), 1.0_a);
EXPECT_EQ(CheckedConvert<i32>(1_i), 1_i);
EXPECT_EQ(CheckedConvert<u32>(1_u), 1_u);
EXPECT_EQ(CheckedConvert<f32>(1_f), 1_f);
EXPECT_EQ(CheckedConvert<f16>(1_h), 1_h);
}
TEST(NumberTest, CheckedConvertLargestValue) {
EXPECT_EQ(CheckedConvert<i32>(AInt(i32::Highest())), i32::Highest());
EXPECT_EQ(CheckedConvert<u32>(AInt(u32::Highest())), u32::Highest());
EXPECT_EQ(CheckedConvert<u32>(i32::Highest()), u32(i32::Highest()));
EXPECT_EQ(CheckedConvert<f32>(AFloat(f32::Highest())), f32::Highest());
EXPECT_EQ(CheckedConvert<f16>(AFloat(f16::Highest())), f16::Highest());
}
TEST(NumberTest, CheckedConvertLowestValue) {
EXPECT_EQ(CheckedConvert<i32>(AInt(i32::Lowest())), i32::Lowest());
EXPECT_EQ(CheckedConvert<u32>(AInt(u32::Lowest())), u32::Lowest());
EXPECT_EQ(CheckedConvert<f32>(AFloat(f32::Lowest())), f32::Lowest());
EXPECT_EQ(CheckedConvert<f16>(AFloat(f16::Lowest())), f16::Lowest());
}
TEST(NumberTest, CheckedConvertSmallestValue) {
EXPECT_EQ(CheckedConvert<i32>(AInt(0)), i32(0));
EXPECT_EQ(CheckedConvert<u32>(AInt(0)), u32(0));
EXPECT_EQ(CheckedConvert<f32>(AFloat(f32::Smallest())), f32::Smallest());
EXPECT_EQ(CheckedConvert<f16>(AFloat(f16::Smallest())), f16::Smallest());
}
TEST(NumberTest, CheckedConvertExceedsPositiveLimit) {
EXPECT_EQ(CheckedConvert<i32>(AInt(static_cast<int64_t>(i32::Highest()) + 1)),
ConversionFailure::kExceedsPositiveLimit);
EXPECT_EQ(CheckedConvert<u32>(AInt(static_cast<uint64_t>(u32::Highest()) + 1)),
ConversionFailure::kExceedsPositiveLimit);
EXPECT_EQ(CheckedConvert<i32>(u32::Highest()), ConversionFailure::kExceedsPositiveLimit);
EXPECT_EQ(CheckedConvert<i32>(u32(0x80000000)), ConversionFailure::kExceedsPositiveLimit);
EXPECT_EQ(CheckedConvert<u32>(f32::Highest()), ConversionFailure::kExceedsPositiveLimit);
EXPECT_EQ(CheckedConvert<i32>(f32::Highest()), ConversionFailure::kExceedsPositiveLimit);
EXPECT_EQ(CheckedConvert<u32>(AFloat::Highest()), ConversionFailure::kExceedsPositiveLimit);
EXPECT_EQ(CheckedConvert<i32>(AFloat::Highest()), ConversionFailure::kExceedsPositiveLimit);
EXPECT_EQ(CheckedConvert<f32>(AFloat(kHighestF32NextULP)),
ConversionFailure::kExceedsPositiveLimit);
EXPECT_EQ(CheckedConvert<f16>(AFloat(kHighestF16NextULP)),
ConversionFailure::kExceedsPositiveLimit);
}
TEST(NumberTest, CheckedConvertExceedsNegativeLimit) {
EXPECT_EQ(CheckedConvert<i32>(AInt(static_cast<int64_t>(i32::Lowest()) - 1)),
ConversionFailure::kExceedsNegativeLimit);
EXPECT_EQ(CheckedConvert<u32>(AInt(static_cast<uint64_t>(u32::Lowest()) - 1)),
ConversionFailure::kExceedsNegativeLimit);
EXPECT_EQ(CheckedConvert<u32>(i32(-1)), ConversionFailure::kExceedsNegativeLimit);
EXPECT_EQ(CheckedConvert<u32>(i32::Lowest()), ConversionFailure::kExceedsNegativeLimit);
EXPECT_EQ(CheckedConvert<u32>(f32::Lowest()), ConversionFailure::kExceedsNegativeLimit);
EXPECT_EQ(CheckedConvert<i32>(f32::Lowest()), ConversionFailure::kExceedsNegativeLimit);
EXPECT_EQ(CheckedConvert<u32>(AFloat::Lowest()), ConversionFailure::kExceedsNegativeLimit);
EXPECT_EQ(CheckedConvert<i32>(AFloat::Lowest()), ConversionFailure::kExceedsNegativeLimit);
EXPECT_EQ(CheckedConvert<f32>(AFloat(kLowestF32NextULP)),
ConversionFailure::kExceedsNegativeLimit);
EXPECT_EQ(CheckedConvert<f16>(AFloat(kLowestF16NextULP)),
ConversionFailure::kExceedsNegativeLimit);
}
TEST(NumberTest, CheckedConvertSubnormals) {
EXPECT_EQ(CheckedConvert<f32>(AFloat(kHighestF32Subnormal)), f32(kHighestF32Subnormal));
EXPECT_EQ(CheckedConvert<f16>(AFloat(kHighestF16Subnormal)), f16(kHighestF16Subnormal));
EXPECT_EQ(CheckedConvert<f32>(AFloat(-kHighestF32Subnormal)), f32(-kHighestF32Subnormal));
EXPECT_EQ(CheckedConvert<f16>(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<float>::quiet_NaN();
constexpr float f32_inf = std::numeric_limits<float>::infinity();
struct F16TestCase {
float input_value;
float quantized_value;
uint16_t f16_bit_pattern;
};
using NumberF16Test = testing::TestWithParam<F16TestCase>;
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<F16TestCase>{
// 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 <typename T>
auto Overflow = std::optional<T>{};
using BinaryCheckedCase_AInt = std::tuple<std::optional<AInt>, AInt, AInt>;
using CheckedAddTest_AInt = testing::TestWithParam<BinaryCheckedCase_AInt>;
using FloatInputTypes = std::variant<AFloat, f32, f16>;
using FloatExpectedTypes =
std::variant<std::optional<AFloat>, std::optional<f32>, std::optional<f16>>;
using BinaryCheckedCase_Float = std::tuple<FloatExpectedTypes, FloatInputTypes, FloatInputTypes>;
/// Validates that result is equal to expect. If `float_comp` is true, uses EXPECT_FLOAT_EQ to
/// compare the values.
template <typename T>
void ValidateResult(std::optional<T> result, std::optional<T> expect, bool float_comp = false) {
if (!expect) {
EXPECT_TRUE(!result) << *result;
} else {
ASSERT_TRUE(result);
if constexpr (IsIntegral<T>) {
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<BinaryCheckedCase_AInt>{
{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<BinaryCheckedCase_Float>;
TEST_P(CheckedAddTest_Float, Test) {
auto& p = GetParam();
std::visit(
[&](auto&& lhs) {
using T = std::decay_t<decltype(lhs)>;
auto rhs = std::get<T>(std::get<2>(p));
auto expect = std::get<std::optional<T>>(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 <typename T>
std::vector<BinaryCheckedCase_Float> 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>, T::Highest(), T::Highest()},
{Overflow<T>, T::Lowest(), T::Lowest()},
};
}
INSTANTIATE_TEST_SUITE_P(CheckedAddTest_Float,
CheckedAddTest_Float,
testing::ValuesIn(Concat(CheckedAddTest_FloatCases<AFloat>(),
CheckedAddTest_FloatCases<f32>(),
CheckedAddTest_FloatCases<f16>())));
using CheckedSubTest_AInt = testing::TestWithParam<BinaryCheckedCase_AInt>;
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<BinaryCheckedCase_AInt>{
{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<BinaryCheckedCase_Float>;
TEST_P(CheckedSubTest_Float, Test) {
auto& p = GetParam();
std::visit(
[&](auto&& lhs) {
using T = std::decay_t<decltype(lhs)>;
auto rhs = std::get<T>(std::get<2>(p));
auto expect = std::get<std::optional<T>>(std::get<0>(p));
ValidateResult(CheckedSub(lhs, rhs), expect);
},
std::get<1>(p));
}
template <typename T>
std::vector<BinaryCheckedCase_Float> 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>, T::Lowest(), T::Highest()},
};
}
INSTANTIATE_TEST_SUITE_P(CheckedSubTest_Float,
CheckedSubTest_Float,
testing::ValuesIn(Concat(CheckedSubTest_FloatCases<AFloat>(),
CheckedSubTest_FloatCases<f32>(),
CheckedSubTest_FloatCases<f16>())));
using CheckedMulTest_AInt = testing::TestWithParam<BinaryCheckedCase_AInt>;
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<BinaryCheckedCase_AInt>{
{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<BinaryCheckedCase_Float>;
TEST_P(CheckedMulTest_Float, Test) {
auto& p = GetParam();
std::visit(
[&](auto&& lhs) {
using T = std::decay_t<decltype(lhs)>;
auto rhs = std::get<T>(std::get<2>(p));
auto expect = std::get<std::optional<T>>(std::get<0>(p));
ValidateResult(CheckedMul(lhs, rhs), expect);
ValidateResult(CheckedMul(rhs, lhs), expect);
},
std::get<1>(p));
}
template <typename T>
std::vector<BinaryCheckedCase_Float> 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>, T::Highest(), T::Highest()},
{Overflow<T>, T::Lowest(), T::Lowest()},
};
}
INSTANTIATE_TEST_SUITE_P(CheckedMulTest_Float,
CheckedMulTest_Float,
testing::ValuesIn(Concat(CheckedMulTest_FloatCases<AFloat>(),
CheckedMulTest_FloatCases<f32>(),
CheckedMulTest_FloatCases<f16>())));
using CheckedDivTest_AInt = testing::TestWithParam<BinaryCheckedCase_AInt>;
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<BinaryCheckedCase_AInt>{
{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<BinaryCheckedCase_Float>;
TEST_P(CheckedDivTest_Float, Test) {
auto& p = GetParam();
std::visit(
[&](auto&& lhs) {
using T = std::decay_t<decltype(lhs)>;
auto rhs = std::get<T>(std::get<2>(p));
auto expect = std::get<std::optional<T>>(std::get<0>(p));
ValidateResult(CheckedDiv(lhs, rhs), expect);
},
std::get<1>(p));
}
template <typename T>
std::vector<BinaryCheckedCase_Float> 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>, T(123), T(0)},
{Overflow<T>, T(123), T(-0)},
{Overflow<T>, T(-123), T(0)},
{Overflow<T>, T(-123), T(-0)},
};
}
INSTANTIATE_TEST_SUITE_P(CheckedDivTest_Float,
CheckedDivTest_Float,
testing::ValuesIn(Concat(CheckedDivTest_FloatCases<AFloat>(),
CheckedDivTest_FloatCases<f32>(),
CheckedDivTest_FloatCases<f16>())));
using CheckedModTest_AInt = testing::TestWithParam<BinaryCheckedCase_AInt>;
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<BinaryCheckedCase_AInt>{
{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<BinaryCheckedCase_Float>;
TEST_P(CheckedModTest_Float, Test) {
auto& p = GetParam();
std::visit(
[&](auto&& lhs) {
using T = std::decay_t<decltype(lhs)>;
auto rhs = std::get<T>(std::get<2>(p));
auto expect = std::get<std::optional<T>>(std::get<0>(p));
ValidateResult(CheckedMod(lhs, rhs), expect);
},
std::get<1>(p));
}
template <typename T>
std::vector<BinaryCheckedCase_Float> 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>, T(123), T(0)}, //
{Overflow<T>, T(123), T(-0)}, //
{Overflow<T>, T(-123), T(0)}, //
{Overflow<T>, T(-123), T(-0)},
};
}
INSTANTIATE_TEST_SUITE_P(CheckedModTest_Float,
CheckedModTest_Float,
testing::ValuesIn(Concat(CheckedModTest_FloatCases<AFloat>(),
CheckedModTest_FloatCases<f32>(),
CheckedModTest_FloatCases<f16>())));
using CheckedPowTest_Float = testing::TestWithParam<BinaryCheckedCase_Float>;
TEST_P(CheckedPowTest_Float, Test) {
auto& p = GetParam();
std::visit(
[&](auto&& lhs) {
using T = std::decay_t<decltype(lhs)>;
auto rhs = std::get<T>(std::get<2>(p));
auto expect = std::get<std::optional<T>>(std::get<0>(p));
ValidateResult(CheckedPow(lhs, rhs), expect, /* float_comp */ true);
},
std::get<1>(p));
}
template <typename T>
std::vector<BinaryCheckedCase_Float> 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>, -T(1), T(1)}, //
{Overflow<T>, -T(1), T::Highest()}, //
{Overflow<T>, T::Lowest(), T(1)}, //
{Overflow<T>, T::Lowest(), T::Highest()}, //
{Overflow<T>, T::Lowest(), T::Lowest()}, //
{Overflow<T>, T(0), T(0)}, //
{Overflow<T>, T(0), -T(1)}, //
{Overflow<T>, T(0), T::Lowest()}, //
};
}
INSTANTIATE_TEST_SUITE_P(CheckedPowTest_Float,
CheckedPowTest_Float,
testing::ValuesIn(Concat(CheckedPowTest_FloatCases<AFloat>(),
CheckedPowTest_FloatCases<f32>(),
CheckedPowTest_FloatCases<f16>())));
using TernaryCheckedCase = std::tuple<std::optional<AInt>, AInt, AInt, AInt>;
using CheckedMaddTest_AInt = testing::TestWithParam<TernaryCheckedCase>;
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<TernaryCheckedCase>{
{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