// the significand is not zero.
significand_is_zero = false;
significand |= first_exponent_bit;
- significand = static_cast<uint_type>(significand >> 1);
+ significand = static_cast<uint_type>(significand >> 1);
}
while (exponent < min_exponent) {
- significand = static_cast<uint_type>(significand >> 1);
+ significand = static_cast<uint_type>(significand >> 1);
++exponent;
}
if (is_nan) {
typename other_T::uint_type shifted_significand;
shifted_significand = static_cast<typename other_T::uint_type>(
- negatable_left_shift<static_cast<int_type>(other_T::num_fraction_bits) -
- static_cast<int_type>(
- num_fraction_bits)>::val(significand));
+ negatable_left_shift<
+ static_cast<int_type>(other_T::num_fraction_bits) -
+ static_cast<int_type>(num_fraction_bits)>::val(significand));
// We are some sort of Nan. We try to keep the bit-pattern of the Nan
// as close as possible. If we had to shift off bits so we are 0, then we
}
// Since this is denormalized, we have to consume the leading 1 since it
// will end up being implicit.
- fraction = static_cast<uint_type>(fraction << 1); // eat the leading 1
+ fraction = static_cast<uint_type>(fraction << 1); // eat the leading 1
fraction &= HF::fraction_represent_mask;
}
// fractional part.
while (fraction_nibbles > 0 && (fraction & 0xF) == 0) {
// Shift off any trailing values;
- fraction = static_cast<uint_type>(fraction >> 4);
+ fraction = static_cast<uint_type>(fraction >> 4);
--fraction_nibbles;
}
return os;
}
+// Returns true if negate_value is true and the next character on the
+// input stream is a plus or minus sign. In that case we also set the fail bit
+// on the stream and set the value to the zero value for its type.
+template <typename T, typename Traits>
+inline bool RejectParseDueToLeadingSign(std::istream& is, bool negate_value,
+ HexFloat<T, Traits>& value) {
+ if (negate_value) {
+ auto next_char = is.peek();
+ 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));
+ is.setstate(std::ios_base::failbit);
+ return true;
+ }
+ }
+ return false;
+}
+
// Parses a floating point number from the given stream and stores it into the
// value parameter.
-// If the negate_value parameter is true then the number is negated before
-// it is stored into the value parameter.
+// 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.
template <typename T, typename Traits>
inline std::istream& ParseNormalFloat(std::istream& is, bool negate_value,
HexFloat<T, Traits>& value) {
+ if (RejectParseDueToLeadingSign(is, negate_value, value)) {
+ return is;
+ }
T val;
is >> val;
if (negate_value) {
// Specialization of ParseNormalFloat for FloatProxy<Float16> values.
// This will parse the float as it were a 32-bit floating point number,
-// and then round it down to fit into a Float16 value.
+// and then round it down to fit into a Float16 value.
// The number is rounded towards zero.
// Any floating point number that is too large will be rounded to +- infinity.
+// 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.
template <>
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) {
if (bits_written) {
// If we are here the bits represented belong in the fractional
// part of the float, and we have to adjust the exponent accordingly.
- fraction =
- static_cast<uint_type>(fraction |
+ fraction = static_cast<uint_type>(
+ fraction |
static_cast<uint_type>(
write_bit << (HF::top_bit_left_shift - fraction_index++)));
exponent = static_cast<int_type>(exponent + 1);
// integer.
exponent = static_cast<int_type>(exponent - 1);
} else {
- fraction =
- static_cast<uint_type>(fraction |
+ fraction = static_cast<uint_type>(
+ fraction |
static_cast<uint_type>(
write_bit << (HF::top_bit_left_shift - fraction_index++)));
}
return os;
}
-template<>
-inline std::ostream& operator<< <Float16>(std::ostream& os, const FloatProxy<Float16>& value) {
+template <>
+inline std::ostream& operator<<<Float16>(std::ostream& os,
+ const FloatProxy<Float16>& value) {
os << HexFloat<FloatProxy<Float16>>(value);
return os;
}
namespace {
using ::testing::Eq;
using spvutils::BitwiseCast;
+using spvutils::Float16;
using spvutils::FloatProxy;
+using spvutils::HexFloat;
+using spvutils::ParseNormalFloat;
// In this file "encode" means converting a number into a string,
// and "decode" means converting a string into a number.
}
}
+// A test case for parsing good and bad HexFloat<FloatProxy<T>> literals.
+template <typename T>
+struct FloatParseCase {
+ std::string literal;
+ bool negate_value;
+ bool expect_success;
+ HexFloat<FloatProxy<T>> expected_value;
+};
+
+using ParseNormalFloatTest = ::testing::TestWithParam<FloatParseCase<float>>;
+
+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;
+ }
+}
+
+// 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};
+}
+
+// Returns a FloatParseCase that should successfully parse to a given value.
+template <typename T>
+FloatParseCase<T> GoodFloatParseCase(std::string literal, bool negate_value,
+ T expected_value) {
+ HexFloat<FloatProxy<T>> proxy_expected_value(expected_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),
+
+ // 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),
+
+ // 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),
+ }));
+
+using ParseNormalFloat16Test =
+ ::testing::TestWithParam<FloatParseCase<Float16>>;
+
+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;
+ }
+}
+
+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),
+
+ // Valid cases.
+ GoodFloatParseCase<Float16>("0", false, uint16_t{0}),
+ GoodFloatParseCase<Float16>("0.0", false, uint16_t{0}),
+ GoodFloatParseCase<Float16>("-0.0", false, uint16_t{0x8000}),
+ GoodFloatParseCase<Float16>("2.0", false, uint16_t{0x4000}),
+ GoodFloatParseCase<Float16>("-2.0", false, uint16_t{0xc000}),
+ GoodFloatParseCase<Float16>("+2.0", false, uint16_t{0x4000}),
+ // Cases with negate_value being true.
+ GoodFloatParseCase<Float16>("0.0", true, uint16_t{0x8000}),
+ GoodFloatParseCase<Float16>("2.0", true, uint16_t{0xc000}),
+
+ // 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),
+ }));
+
// TODO(awoloszyn): Add fp16 tests and HexFloatTraits.
}