mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-24 00:40:14 +00:00
Float literal parsing fixes
- For 32- and 64-bit floats, overflow is a parse error This works around a difference between Xcode's istringstream and other platforms. Xcode's runtime library will happlily "round up" overflow values to infinity. We want to make it fail. - When parsing a float fails due to bad syntax, follow C++11 behaviour for operator>> and set the value to zero. - When parsing a 32-bit or 64-bit float overflows, follow C++11 behaviour for operator>> and set the value to the nearest normal value: either max or lowest finite value for the type. - Add FloatProxy<T>::max() and ::lowest() - Make 16-bit overflow behaviour more consistent: we always get a 16-bit infinity of the right sign, whether the original string is a normal value for 32-bit or an overflow value for 32-bit. That matches our earlier intent. Added TODO's to make 16-bit overflow always an error, just like for 32-bit and 64-bit. - Simplify normal parsing of Float16 values by delegating to normal parsing of 32-bit floats.
This commit is contained in:
parent
000cad9cc6
commit
6bad02c320
@ -32,7 +32,6 @@
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
|
||||
#include "bitutils.h"
|
||||
@ -46,9 +45,18 @@ class Float16 {
|
||||
static bool isNan(const Float16 val) {
|
||||
return ((val.val & 0x7C00) == 0x7C00) && ((val.val & 0x3FF) != 0);
|
||||
}
|
||||
// Returns true if the given value is any kind of infinity.
|
||||
static bool isInfinity(const Float16 val) {
|
||||
return ((val.val & 0x7C00) == 0x7C00) && ((val.val & 0x3FF) == 0);
|
||||
}
|
||||
Float16(const Float16& other) { val = other.val; }
|
||||
uint16_t get_value() const { return val; }
|
||||
|
||||
// Returns the maximum normal value.
|
||||
static Float16 max() { return Float16(0x7bff); }
|
||||
// Returns the lowest normal value.
|
||||
static Float16 lowest() { return Float16(0xfbff); }
|
||||
|
||||
private:
|
||||
uint16_t val;
|
||||
};
|
||||
@ -66,18 +74,36 @@ template <>
|
||||
struct FloatProxyTraits<float> {
|
||||
using uint_type = uint32_t;
|
||||
static bool isNan(float f) { return std::isnan(f); }
|
||||
// Returns true if the given value is any kind of infinity.
|
||||
static bool isInfinity(float f) { return std::isinf(f); }
|
||||
// Returns the maximum normal value.
|
||||
static float max() { return std::numeric_limits<float>::max(); }
|
||||
// Returns the lowest normal value.
|
||||
static float lowest() { return std::numeric_limits<float>::lowest(); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct FloatProxyTraits<double> {
|
||||
using uint_type = uint64_t;
|
||||
static bool isNan(double f) { return std::isnan(f); }
|
||||
// Returns true if the given value is any kind of infinity.
|
||||
static bool isInfinity(double f) { return std::isinf(f); }
|
||||
// Returns the maximum normal value.
|
||||
static double max() { return std::numeric_limits<double>::max(); }
|
||||
// Returns the lowest normal value.
|
||||
static double lowest() { return std::numeric_limits<double>::lowest(); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct FloatProxyTraits<Float16> {
|
||||
using uint_type = uint16_t;
|
||||
static bool isNan(Float16 f) { return Float16::isNan(f); }
|
||||
// Returns true if the given value is any kind of infinity.
|
||||
static bool isInfinity(Float16 f) { return Float16::isInfinity(f); }
|
||||
// Returns the maximum normal value.
|
||||
static Float16 max() { return Float16::max(); }
|
||||
// Returns the lowest normal value.
|
||||
static Float16 lowest() { return Float16::lowest(); }
|
||||
};
|
||||
|
||||
// Since copying a floating point number (especially if it is NaN)
|
||||
@ -114,6 +140,17 @@ class FloatProxy {
|
||||
|
||||
// Returns true if the value represents any type of NaN.
|
||||
bool isNan() { return FloatProxyTraits<T>::isNan(getAsFloat()); }
|
||||
// Returns true if the value represents any type of infinity.
|
||||
bool isInfinity() { return FloatProxyTraits<T>::isInfinity(getAsFloat()); }
|
||||
|
||||
// Returns the maximum normal value.
|
||||
static FloatProxy<T> max() {
|
||||
return FloatProxy<T>(FloatProxyTraits<T>::max());
|
||||
}
|
||||
// Returns the lowest normal value.
|
||||
static FloatProxy<T> lowest() {
|
||||
return FloatProxy<T>(FloatProxyTraits<T>::lowest());
|
||||
}
|
||||
|
||||
private:
|
||||
uint_type data_;
|
||||
@ -722,7 +759,7 @@ inline bool RejectParseDueToLeadingSign(std::istream& is, bool negate_value,
|
||||
if (next_char == '-' || next_char == '+') {
|
||||
// Fail the parse. Emulate standard behaviour by setting the value to
|
||||
// the zero value, and set the fail bit on the stream.
|
||||
value = HexFloat<T, Traits>(typename HexFloat<T, Traits>::uint_type(0));
|
||||
value = HexFloat<T, Traits>(typename HexFloat<T, Traits>::uint_type{0});
|
||||
is.setstate(std::ios_base::failbit);
|
||||
return true;
|
||||
}
|
||||
@ -735,6 +772,11 @@ inline bool RejectParseDueToLeadingSign(std::istream& is, bool negate_value,
|
||||
// If negate_value is true then the number may not have a leading minus or
|
||||
// plus, and if it successfully parses, then the number is negated before
|
||||
// being stored into the value parameter.
|
||||
// If the value cannot be correctly parsed, then set the fail bit on the
|
||||
// stream, and set the value to zero.
|
||||
// If the value overflows the target floating point type, then set the fail
|
||||
// bit on the stream and set the value to the nearest finite value for the
|
||||
// type, which can either be positive or negative.
|
||||
template <typename T, typename Traits>
|
||||
inline std::istream& ParseNormalFloat(std::istream& is, bool negate_value,
|
||||
HexFloat<T, Traits>& value) {
|
||||
@ -747,6 +789,17 @@ inline std::istream& ParseNormalFloat(std::istream& is, bool negate_value,
|
||||
val = -val;
|
||||
}
|
||||
value.set_value(val);
|
||||
// In the failure case, map -0.0 to 0.0.
|
||||
if (is.fail() && value.getUnsignedBits() == 0u) {
|
||||
value = HexFloat<T, Traits>(typename HexFloat<T, Traits>::uint_type{0});
|
||||
}
|
||||
if (val.isInfinity()) {
|
||||
// Fail the parse. Emulate standard behaviour by setting the value to
|
||||
// the closest normal value, and set the fail bit on the stream.
|
||||
value.set_value((value.isNegative() | negate_value) ? T::lowest()
|
||||
: T::max());
|
||||
is.setstate(std::ios_base::failbit);
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
@ -763,16 +816,23 @@ inline std::istream&
|
||||
ParseNormalFloat<FloatProxy<Float16>, HexFloatTraits<FloatProxy<Float16>>>(
|
||||
std::istream& is, bool negate_value,
|
||||
HexFloat<FloatProxy<Float16>, HexFloatTraits<FloatProxy<Float16>>>& value) {
|
||||
if (RejectParseDueToLeadingSign(is, negate_value, value)) {
|
||||
return is;
|
||||
}
|
||||
float f;
|
||||
is >> f;
|
||||
if (negate_value) {
|
||||
f = -f;
|
||||
}
|
||||
HexFloat<FloatProxy<float>> float_val(f);
|
||||
// First parse as a 32-bit float.
|
||||
HexFloat<FloatProxy<float>> float_val(0.0f);
|
||||
ParseNormalFloat(is, negate_value, float_val);
|
||||
|
||||
// Then convert to 16-bit float, saturating at infinities, and
|
||||
// rounding toward zero.
|
||||
float_val.castTo(value, round_direction::kToZero);
|
||||
|
||||
// Our (current) rule is to allow overflow in 16-bit floats
|
||||
// to validly map to infinities. But we might have overflowed the
|
||||
// 32-bit float in the first place and set the fail bit on the stream.
|
||||
// If we did, then reset the fail bit.
|
||||
// TODO(dneto): Overflow on 16-bit should behave the same as for 32- and
|
||||
// 64-bit. It should set the fail bit and set the lowest or highest value.
|
||||
if (Float16::isInfinity(value.value().getAsFloat())) {
|
||||
is.clear(is.rdstate() & ~std::ios_base::failbit);
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
||||
|
||||
#include <cfloat>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
@ -1026,21 +1027,20 @@ TEST_P(ParseNormalFloatTest, Samples) {
|
||||
std::stringstream input(GetParam().literal);
|
||||
HexFloat<FloatProxy<float>> parsed_value(0.0f);
|
||||
ParseNormalFloat(input, GetParam().negate_value, parsed_value);
|
||||
if (GetParam().expect_success) {
|
||||
EXPECT_FALSE(input.fail()) << " literal: " << GetParam().literal
|
||||
<< " negate: " << GetParam().negate_value;
|
||||
EXPECT_THAT(parsed_value.value(), Eq(GetParam().expected_value.value()));
|
||||
} else {
|
||||
EXPECT_TRUE(input.fail()) << " literal: " << GetParam().literal
|
||||
<< " negate: " << GetParam().negate_value;
|
||||
}
|
||||
EXPECT_NE(GetParam().expect_success, input.fail())
|
||||
<< " literal: " << GetParam().literal
|
||||
<< " negate: " << GetParam().negate_value;
|
||||
EXPECT_THAT(parsed_value.value(), Eq(GetParam().expected_value.value()))
|
||||
<< " literal: " << GetParam().literal
|
||||
<< " negate: " << GetParam().negate_value;
|
||||
}
|
||||
|
||||
// Returns a FloatParseCase with expected failure.
|
||||
template <typename T>
|
||||
FloatParseCase<T> BadFloatParseCase(std::string literal, bool negate_value) {
|
||||
HexFloat<FloatProxy<T>> dummy_value(0.0f);
|
||||
return FloatParseCase<T>{literal, negate_value, false, dummy_value};
|
||||
FloatParseCase<T> BadFloatParseCase(std::string literal, bool negate_value,
|
||||
T expected_value) {
|
||||
HexFloat<FloatProxy<T>> proxy_expected_value(expected_value);
|
||||
return FloatParseCase<T>{literal, negate_value, false, proxy_expected_value};
|
||||
}
|
||||
|
||||
// Returns a FloatParseCase that should successfully parse to a given value.
|
||||
@ -1051,30 +1051,39 @@ FloatParseCase<T> GoodFloatParseCase(std::string literal, bool negate_value,
|
||||
return FloatParseCase<T>{literal, negate_value, true, proxy_expected_value};
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(FloatParse, ParseNormalFloatTest,
|
||||
::testing::ValuesIn(std::vector<FloatParseCase<float>>{
|
||||
// Failing cases due to trivially incorrect syntax.
|
||||
BadFloatParseCase<float>("abc", false),
|
||||
BadFloatParseCase<float>("abc", true),
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FloatParse, ParseNormalFloatTest,
|
||||
::testing::ValuesIn(std::vector<FloatParseCase<float>>{
|
||||
// Failing cases due to trivially incorrect syntax.
|
||||
BadFloatParseCase("abc", false, 0.0f),
|
||||
BadFloatParseCase("abc", true, 0.0f),
|
||||
|
||||
// Valid cases.
|
||||
GoodFloatParseCase<float>("0", false, 0.0f),
|
||||
GoodFloatParseCase<float>("0.0", false, 0.0f),
|
||||
GoodFloatParseCase<float>("-0.0", false, -0.0f),
|
||||
GoodFloatParseCase<float>("2.0", false, 2.0f),
|
||||
GoodFloatParseCase<float>("-2.0", false, -2.0f),
|
||||
GoodFloatParseCase<float>("+2.0", false, 2.0f),
|
||||
// Cases with negate_value being true.
|
||||
GoodFloatParseCase<float>("0.0", true, -0.0f),
|
||||
GoodFloatParseCase<float>("2.0", true, -2.0f),
|
||||
// Valid cases.
|
||||
GoodFloatParseCase("0", false, 0.0f),
|
||||
GoodFloatParseCase("0.0", false, 0.0f),
|
||||
GoodFloatParseCase("-0.0", false, -0.0f),
|
||||
GoodFloatParseCase("2.0", false, 2.0f),
|
||||
GoodFloatParseCase("-2.0", false, -2.0f),
|
||||
GoodFloatParseCase("+2.0", false, 2.0f),
|
||||
// Cases with negate_value being true.
|
||||
GoodFloatParseCase("0.0", true, -0.0f),
|
||||
GoodFloatParseCase("2.0", true, -2.0f),
|
||||
|
||||
// When negate_value is true, we should not accept a
|
||||
// leading minus or plus.
|
||||
BadFloatParseCase<float>("-0.0", true),
|
||||
BadFloatParseCase<float>("-2.0", true),
|
||||
BadFloatParseCase<float>("+0.0", true),
|
||||
BadFloatParseCase<float>("+2.0", true),
|
||||
}));
|
||||
// When negate_value is true, we should not accept a
|
||||
// leading minus or plus.
|
||||
BadFloatParseCase("-0.0", true, 0.0f),
|
||||
BadFloatParseCase("-2.0", true, 0.0f),
|
||||
BadFloatParseCase("+0.0", true, 0.0f),
|
||||
BadFloatParseCase("+2.0", true, 0.0f),
|
||||
|
||||
// Overflow is an error for 32-bit float parsing.
|
||||
BadFloatParseCase("1e40", false, FLT_MAX),
|
||||
BadFloatParseCase("1e40", true, -FLT_MAX),
|
||||
BadFloatParseCase("-1e40", false, -FLT_MAX),
|
||||
// We can't have -1e40 and negate_value == true since
|
||||
// that represents an original case of "--1e40" which
|
||||
// is invalid.
|
||||
}));
|
||||
|
||||
using ParseNormalFloat16Test =
|
||||
::testing::TestWithParam<FloatParseCase<Float16>>;
|
||||
@ -1083,22 +1092,20 @@ TEST_P(ParseNormalFloat16Test, Samples) {
|
||||
std::stringstream input(GetParam().literal);
|
||||
HexFloat<FloatProxy<Float16>> parsed_value(0.0f);
|
||||
ParseNormalFloat(input, GetParam().negate_value, parsed_value);
|
||||
if (GetParam().expect_success) {
|
||||
EXPECT_FALSE(input.fail()) << " literal: " << GetParam().literal
|
||||
<< " negate: " << GetParam().negate_value;
|
||||
EXPECT_THAT(parsed_value.value(), Eq(GetParam().expected_value.value()));
|
||||
} else {
|
||||
EXPECT_TRUE(input.fail()) << " literal: " << GetParam().literal
|
||||
<< " negate: " << GetParam().negate_value;
|
||||
}
|
||||
EXPECT_NE(GetParam().expect_success, input.fail())
|
||||
<< " literal: " << GetParam().literal
|
||||
<< " negate: " << GetParam().negate_value;
|
||||
EXPECT_THAT(parsed_value.value(), Eq(GetParam().expected_value.value()))
|
||||
<< " literal: " << GetParam().literal
|
||||
<< " negate: " << GetParam().negate_value;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Float16Parse, ParseNormalFloat16Test,
|
||||
::testing::ValuesIn(std::vector<FloatParseCase<Float16>>{
|
||||
// Failing cases due to trivially incorrect syntax.
|
||||
BadFloatParseCase<Float16>("abc", false),
|
||||
BadFloatParseCase<Float16>("abc", true),
|
||||
BadFloatParseCase<Float16>("abc", false, uint16_t{0}),
|
||||
BadFloatParseCase<Float16>("abc", true, uint16_t{0}),
|
||||
|
||||
// Valid cases.
|
||||
GoodFloatParseCase<Float16>("0", false, uint16_t{0}),
|
||||
@ -1113,11 +1120,118 @@ INSTANTIATE_TEST_CASE_P(
|
||||
|
||||
// When negate_value is true, we should not accept a leading minus or
|
||||
// plus.
|
||||
BadFloatParseCase<Float16>("-0.0", true),
|
||||
BadFloatParseCase<Float16>("-2.0", true),
|
||||
BadFloatParseCase<Float16>("+0.0", true),
|
||||
BadFloatParseCase<Float16>("+2.0", true),
|
||||
BadFloatParseCase<Float16>("-0.0", true, uint16_t{0}),
|
||||
BadFloatParseCase<Float16>("-2.0", true, uint16_t{0}),
|
||||
BadFloatParseCase<Float16>("+0.0", true, uint16_t{0}),
|
||||
BadFloatParseCase<Float16>("+2.0", true, uint16_t{0}),
|
||||
}));
|
||||
|
||||
// TODO(awoloszyn): Add fp16 tests and HexFloatTraits.
|
||||
// A test case for detecting infinities.
|
||||
template <typename T>
|
||||
struct OverflowParseCase {
|
||||
std::string input;
|
||||
bool expect_success;
|
||||
T expected_value;
|
||||
};
|
||||
|
||||
using FloatProxyParseOverflowFloatTest =
|
||||
::testing::TestWithParam<OverflowParseCase<float>>;
|
||||
|
||||
TEST_P(FloatProxyParseOverflowFloatTest, Sample) {
|
||||
std::istringstream input(GetParam().input);
|
||||
HexFloat<FloatProxy<float>> value(0.0f);
|
||||
input >> value;
|
||||
EXPECT_NE(GetParam().expect_success, input.fail());
|
||||
EXPECT_THAT(value.value().getAsFloat(), GetParam().expected_value);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FloatOverflow, FloatProxyParseOverflowFloatTest,
|
||||
::testing::ValuesIn(std::vector<OverflowParseCase<float>>({
|
||||
{"0", true, 0.0f},
|
||||
{"0.0", true, 0.0f},
|
||||
{"1.0", true, 1.0f},
|
||||
{"1e38", true, 1e38f},
|
||||
{"-1e38", true, -1e38f},
|
||||
{"1e40", false, FLT_MAX},
|
||||
{"-1e40", false, -FLT_MAX},
|
||||
{"1e400", false, FLT_MAX},
|
||||
{"-1e400", false, -FLT_MAX},
|
||||
})));
|
||||
|
||||
using FloatProxyParseOverflowDoubleTest =
|
||||
::testing::TestWithParam<OverflowParseCase<double>>;
|
||||
|
||||
TEST_P(FloatProxyParseOverflowDoubleTest, Sample) {
|
||||
std::istringstream input(GetParam().input);
|
||||
HexFloat<FloatProxy<double>> value(0.0);
|
||||
input >> value;
|
||||
EXPECT_NE(GetParam().expect_success, input.fail());
|
||||
EXPECT_THAT(value.value().getAsFloat(), Eq(GetParam().expected_value));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DoubleOverflow, FloatProxyParseOverflowDoubleTest,
|
||||
::testing::ValuesIn(std::vector<OverflowParseCase<double>>({
|
||||
{"0", true, 0.0},
|
||||
{"0.0", true, 0.0},
|
||||
{"1.0", true, 1.0},
|
||||
{"1e38", true, 1e38},
|
||||
{"-1e38", true, -1e38},
|
||||
{"1e40", true, 1e40},
|
||||
{"-1e40", true, -1e40},
|
||||
{"1e400", false, DBL_MAX},
|
||||
{"-1e400", false, -DBL_MAX},
|
||||
})));
|
||||
|
||||
using FloatProxyParseOverflowFloat16Test =
|
||||
::testing::TestWithParam<OverflowParseCase<uint16_t>>;
|
||||
|
||||
TEST_P(FloatProxyParseOverflowFloat16Test, Sample) {
|
||||
std::istringstream input(GetParam().input);
|
||||
HexFloat<FloatProxy<Float16>> value(0);
|
||||
input >> value;
|
||||
EXPECT_NE(GetParam().expect_success, input.fail()) << " literal: "
|
||||
<< GetParam().input;
|
||||
EXPECT_THAT(value.value().data(), Eq(GetParam().expected_value))
|
||||
<< " literal: " << GetParam().input;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Float16Overflow, FloatProxyParseOverflowFloat16Test,
|
||||
::testing::ValuesIn(std::vector<OverflowParseCase<uint16_t>>({
|
||||
// For Float16, too-large values are parsed as
|
||||
// infinities, and also valid.
|
||||
// TODO(dneto): Overflow for 16-bit float should be an error,
|
||||
// just like for 32-bit and 64-bit.
|
||||
{"0", true, uint16_t{0}},
|
||||
{"0.0", true, uint16_t{0}},
|
||||
{"1.0", true, uint16_t{0x3c00}},
|
||||
{"1e38", true, uint16_t{0x7c00}},
|
||||
{"-1e38", true, uint16_t{0xfc00}},
|
||||
{"1e40", true, uint16_t{0x7c00}},
|
||||
{"1e400", true, uint16_t{0x7c00}},
|
||||
{"-1e40", true, uint16_t{0xfc00}},
|
||||
{"-1e400", true, uint16_t{0xfc00}},
|
||||
})));
|
||||
|
||||
TEST(FloatProxy, Max) {
|
||||
EXPECT_THAT(FloatProxy<Float16>::max().getAsFloat().get_value(),
|
||||
Eq(uint16_t{0x7bff}));
|
||||
EXPECT_THAT(FloatProxy<float>::max().getAsFloat(),
|
||||
Eq(std::numeric_limits<float>::max()));
|
||||
EXPECT_THAT(FloatProxy<double>::max().getAsFloat(),
|
||||
Eq(std::numeric_limits<double>::max()));
|
||||
}
|
||||
|
||||
TEST(FloatProxy, Lowest) {
|
||||
EXPECT_THAT(FloatProxy<Float16>::lowest().getAsFloat().get_value(),
|
||||
Eq(uint16_t{0xfbff}));
|
||||
EXPECT_THAT(FloatProxy<float>::lowest().getAsFloat(),
|
||||
Eq(std::numeric_limits<float>::lowest()));
|
||||
EXPECT_THAT(FloatProxy<double>::lowest().getAsFloat(),
|
||||
Eq(std::numeric_limits<double>::lowest()));
|
||||
}
|
||||
|
||||
// TODO(awoloszyn): Add fp16 tests and HexFloatTraits.
|
||||
} // anonymous namespace
|
||||
|
@ -301,6 +301,7 @@ TEST_P(OpConstantInvalidFloatConstant, Samples) {
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
TextToBinaryInvalidFloatConstant, OpConstantInvalidFloatConstant,
|
||||
::testing::ValuesIn(std::vector<InvalidFloatConstantCase>{
|
||||
@ -309,17 +310,26 @@ INSTANTIATE_TEST_CASE_P(
|
||||
{16, "-+1"},
|
||||
{16, "+-1"},
|
||||
{16, "++1"},
|
||||
// TODO(dneto): Overflow for 16-bit floats should be an error,
|
||||
// just like for 32-bit and 64-bit.
|
||||
{32, "abc"},
|
||||
{32, "--1"},
|
||||
{32, "-+1"},
|
||||
{32, "+-1"},
|
||||
{32, "++1"},
|
||||
{32, "1e40"}, // Overflow is an error for 32-bit floats.
|
||||
{32, "-1e40"},
|
||||
{32, "1e400"},
|
||||
{32, "-1e400"},
|
||||
{64, "abc"},
|
||||
{64, "--1"},
|
||||
{64, "-+1"},
|
||||
{64, "+-1"},
|
||||
{64, "++1"},
|
||||
{32, "1e400"}, // Overflow is an error for 64-bit floats.
|
||||
{32, "-1e400"},
|
||||
}));
|
||||
// clang-format on
|
||||
|
||||
using OpConstantInvalidTypeTest =
|
||||
spvtest::TextToBinaryTestBase<::testing::TestWithParam<std::string>>;
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "UnitSPIRV.h"
|
||||
#include "source/spirv_constant.h"
|
||||
#include "util/bitutils.h"
|
||||
#include "util/hex_float.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@ -260,9 +261,9 @@ INSTANTIATE_TEST_CASE_P(
|
||||
::testing::ValuesIn(std::vector<std::pair<std::string, uint32_t>>{
|
||||
{"0.0", 0x00000000},
|
||||
{"1.0", 0x00003c00},
|
||||
{"1.000844", 0x00003c00}, // Truncate to 1.0
|
||||
{"1.000977", 0x00003c01}, // Don't have to truncate
|
||||
{"1.001465", 0x00003c01}, // Truncate to 1.0000977
|
||||
{"1.000844", 0x00003c00}, // Truncate to 1.0
|
||||
{"1.000977", 0x00003c01}, // Don't have to truncate
|
||||
{"1.001465", 0x00003c01}, // Truncate to 1.0000977
|
||||
{"1.5", 0x00003e00},
|
||||
{"-1.0", 0x0000bc00},
|
||||
{"2.0", 0x00004000},
|
||||
@ -438,9 +439,24 @@ TEST(AssemblyContextParseFloat, Sample) {
|
||||
EXPECT_EQ(1e38f, f);
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-1e38", ec, &f, ""));
|
||||
EXPECT_EQ(-1e38f, f);
|
||||
}
|
||||
|
||||
// Out of range.
|
||||
TEST(AssemblyContextParseFloat, Infinities) {
|
||||
// The assembler parses using HexFloat<FloatProxy<float>>. Make
|
||||
// sure that succeeds for in-range values, and fails for out of
|
||||
// range values.
|
||||
AssemblyContext context(AutoText(""), nullptr);
|
||||
const spv_result_t ec = SPV_FAILED_MATCH;
|
||||
spvutils::HexFloat<spvutils::FloatProxy<float>> f(0.0f);
|
||||
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("1e38", ec, &f, ""));
|
||||
EXPECT_EQ(1e38f, f.value().getAsFloat());
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-1e38", ec, &f, ""));
|
||||
EXPECT_EQ(-1e38f, f.value().getAsFloat());
|
||||
EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("1e40", ec, &f, ""));
|
||||
EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("-1e40", ec, &f, ""));
|
||||
EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("1e400", ec, &f, ""));
|
||||
EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("-1e400", ec, &f, ""));
|
||||
}
|
||||
|
||||
TEST(AssemblyContextParseDouble, Sample) {
|
||||
@ -469,12 +485,54 @@ TEST(AssemblyContextParseDouble, Sample) {
|
||||
EXPECT_EQ(1e40, f);
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-1e40", ec, &f, ""));
|
||||
EXPECT_EQ(-1e40, f);
|
||||
}
|
||||
|
||||
// Out of range.
|
||||
TEST(AssemblyContextParseDouble, Infinities) {
|
||||
// The assembler parses using HexFloat<FloatProxy<double>>. Make
|
||||
// sure that succeeds for in-range values, and fails for out of
|
||||
// range values.
|
||||
AssemblyContext context(AutoText(""), nullptr);
|
||||
const spv_result_t ec = SPV_FAILED_MATCH;
|
||||
spvutils::HexFloat<spvutils::FloatProxy<double>> f(0.0);
|
||||
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("1e38", ec, &f, ""));
|
||||
EXPECT_EQ(1e38, f.value().getAsFloat());
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-1e38", ec, &f, ""));
|
||||
EXPECT_EQ(-1e38, f.value().getAsFloat());
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("1e40", ec, &f, ""));
|
||||
EXPECT_EQ(1e40, f.value().getAsFloat());
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-1e40", ec, &f, ""));
|
||||
EXPECT_EQ(-1e40, f.value().getAsFloat());
|
||||
EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("1e400", ec, &f, ""));
|
||||
EXPECT_EQ(SPV_FAILED_MATCH, context.parseNumber("-1e400", ec, &f, ""));
|
||||
}
|
||||
|
||||
TEST(AssemblyContextParseFloat16, Infinities) {
|
||||
// The assembler parses using HexFloat<FloatProxy<Float16>>. Make
|
||||
// sure that succeeds for in-range values, and returns infinities
|
||||
// for out of range values.
|
||||
AssemblyContext context(AutoText(""), nullptr);
|
||||
const spv_result_t ec = SPV_FAILED_MATCH;
|
||||
spvutils::HexFloat<spvutils::FloatProxy<spvutils::Float16>> f(0.0f);
|
||||
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("0", ec, &f, ""));
|
||||
EXPECT_TRUE(!f.value().isInfinity());
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("1.5", ec, &f, ""));
|
||||
EXPECT_TRUE(!f.value().isInfinity());
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("1e38", ec, &f, ""));
|
||||
EXPECT_TRUE(f.value().isInfinity());
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-1e38", ec, &f, ""));
|
||||
EXPECT_TRUE(f.value().isInfinity());
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("1e40", ec, &f, ""));
|
||||
EXPECT_TRUE(f.value().isInfinity());
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-1e40", ec, &f, ""));
|
||||
EXPECT_TRUE(f.value().isInfinity());
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("1e400", ec, &f, ""));
|
||||
EXPECT_TRUE(f.value().isInfinity());
|
||||
EXPECT_EQ(SPV_SUCCESS, context.parseNumber("-1e400", ec, &f, ""));
|
||||
EXPECT_TRUE(f.value().isInfinity());
|
||||
}
|
||||
|
||||
TEST(AssemblyContextParseMessages, Errors) {
|
||||
spv_diagnostic diag = nullptr;
|
||||
const spv_result_t ec = SPV_FAILED_MATCH;
|
||||
|
Loading…
Reference in New Issue
Block a user