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:
David Neto 2016-02-06 13:48:05 -05:00
parent 000cad9cc6
commit 6bad02c320
4 changed files with 306 additions and 64 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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>>;

View File

@ -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;