From 05a3fead089de46fdd34a87bc108dc29aa0125d0 Mon Sep 17 00:00:00 2001 From: Florian Loitsch Date: Sat, 8 Sep 2018 23:54:37 +0200 Subject: [PATCH] Add support for hexadecimal float literals. Fixes #48. --- double-conversion/double-conversion.cc | 103 +++++++++++++++++-- double-conversion/double-conversion.h | 6 ++ test/cctest/test-conversions.cc | 135 ++++++++++++++++++++++++- 3 files changed, 233 insertions(+), 11 deletions(-) diff --git a/double-conversion/double-conversion.cc b/double-conversion/double-conversion.cc index 7c8e9c67..ba83beff 100644 --- a/double-conversion/double-conversion.cc +++ b/double-conversion/double-conversion.cc @@ -537,7 +537,7 @@ static bool IsDecimalDigitForRadix(int c, int radix) { #pragma optimize("",on) #else static bool inline IsDecimalDigitForRadix(int c, int radix) { - return '0' <= c && c <= '9' && (c - '0') < radix; + return '0' <= c && c <= '9' && (c - '0') < radix; } #endif // Returns true if 'c' is a character digit that is valid for the given radix. @@ -551,17 +551,57 @@ static bool IsCharacterDigitForRadix(int c, int radix, char a_character) { return radix > 10 && c >= a_character && c < a_character + radix - 10; } +// Checks whether the string in the range start-end is a hex-float string. +// This function assumes that the leading '0x'/'0X' is already consumed. +// +// Hex float strings are of one of the following forms: +// - hex_digits+ 'p' ('+'|'-')? exponent_digits+ +// - hex_digits* '.' hex_digits+ 'p' ('+'|'-')? exponent_digits+ +// - hex_digits+ '.' 'p' ('+'|'-')? exponent_digits+ +template +static bool IsHexFloatString(Iterator start, + Iterator end, + bool allow_trailing_junk) { + ASSERT(start != end); + + Iterator current = start; + + while (current != end && isDigit(*current, 16)) ++current; + if (current == end) return false; + if (*current == '.') { + ++current; + while (current != end && isDigit(*current, 16)) ++current; + if (current - start == 1) return false; // Only the '.', but no digits. + } + if (current == end) return false; + if (*current != 'p' && *current != 'P') return false; + ++current; + if (current == end) return false; + if (*current == '+' || *current == '-') ++current; + if (current == end) return false; + if (!isDigit(*current, 10)) return false; + ++current; + while (current != end && isDigit(*current, 10)) ++current; + return allow_trailing_junk || !AdvanceToNonspace(¤t, end); +} + // Parsing integers with radix 2, 4, 8, 16, 32. Assumes current != end. +// +// If parse_as_hex_float is true, then the string must be a valid +// hex-float. template static double RadixStringToIeee(Iterator* current, Iterator end, bool sign, + bool parse_as_hex_float, bool allow_trailing_junk, double junk_string_value, bool read_as_double, bool* result_is_junk) { ASSERT(*current != end); + ASSERT(!parse_as_hex_float || + IsHexFloatString(*current, end, allow_trailing_junk)); const int kDoubleSize = Double::kSignificandSize; const int kSingleSize = Single::kSignificandSize; @@ -581,15 +621,27 @@ static double RadixStringToIeee(Iterator* current, int64_t number = 0; int exponent = 0; const int radix = (1 << radix_log_2); + // Whether we have encountered a '.' and are parsing the decimal digits. + // Only relevant if parse_as_hex_float is true. + bool post_decimal = false; do { int digit; if (IsDecimalDigitForRadix(**current, radix)) { digit = static_cast(**current) - '0'; + if (post_decimal) exponent -= radix_log_2; } else if (IsCharacterDigitForRadix(**current, radix, 'a')) { digit = static_cast(**current) - 'a' + 10; + if (post_decimal) exponent -= radix_log_2; } else if (IsCharacterDigitForRadix(**current, radix, 'A')) { digit = static_cast(**current) - 'A' + 10; + if (post_decimal) exponent -= radix_log_2; + } else if (parse_as_hex_float && **current == '.') { + post_decimal = true; + ++(*current); + continue; + } else if (parse_as_hex_float && (**current == 'p' || **current == 'P')) { + break; } else { if (allow_trailing_junk || !AdvanceToNonspace(current, end)) { break; @@ -612,17 +664,25 @@ static double RadixStringToIeee(Iterator* current, int dropped_bits_mask = ((1 << overflow_bits_count) - 1); int dropped_bits = static_cast(number) & dropped_bits_mask; number >>= overflow_bits_count; - exponent = overflow_bits_count; + exponent += overflow_bits_count; bool zero_tail = true; for (;;) { ++(*current); + if (parse_as_hex_float && **current == '.') { + // Just run over the '.'. We are just trying to see whether there is + // a non-zero digit somewhere. + ++(*current); + post_decimal = true; + } if (*current == end || !isDigit(**current, radix)) break; zero_tail = zero_tail && **current == '0'; - exponent += radix_log_2; + if (!post_decimal) exponent += radix_log_2; } - if (!allow_trailing_junk && AdvanceToNonspace(current, end)) { + if (!parse_as_hex_float && + !allow_trailing_junk && + AdvanceToNonspace(current, end)) { return junk_string_value; } @@ -652,7 +712,26 @@ static double RadixStringToIeee(Iterator* current, *result_is_junk = false; - if (exponent == 0) { + if (parse_as_hex_float) { + ASSERT(**current == 'p' || **current == 'P'); + ++(*current); + bool is_negative = false; + if (**current == '+') { + ++(*current); + } else if (**current == '-') { + is_negative = true; + ++(*current); + } + int written_exponent = 0; + while (*current != end && IsDecimalDigitForRadix(**current, 10)) { + written_exponent = 10 * written_exponent + **current - '0'; + ++(*current); + } + if (is_negative) written_exponent = -written_exponent; + exponent += written_exponent; + } + + if (exponent == 0 || number == 0) { if (sign) { if (number == 0) return -0.0; number = -number; @@ -779,16 +858,23 @@ double StringToDoubleConverter::StringToIeee( leading_zero = true; // It could be hexadecimal value. - if ((flags_ & ALLOW_HEX) && (*current == 'x' || *current == 'X')) { + if (((flags_ & ALLOW_HEX) || (flags_ & ALLOW_HEX_FLOATS)) && + (*current == 'x' || *current == 'X')) { ++current; - if (current == end || !isDigit(*current, 16)) { - return junk_string_value_; // "0x". + + bool parse_as_hex_float = (flags_ & ALLOW_HEX_FLOATS) && + IsHexFloatString(current, end, allow_trailing_junk); + + if (current == end) return junk_string_value_; // "0x" + if (!parse_as_hex_float && !isDigit(*current, 16)) { + return junk_string_value_; } bool result_is_junk; double result = RadixStringToIeee<4>(¤t, end, sign, + parse_as_hex_float, allow_trailing_junk, junk_string_value_, read_as_double, @@ -959,6 +1045,7 @@ double StringToDoubleConverter::StringToIeee( result = RadixStringToIeee<3>(&start, buffer + buffer_pos, sign, + false, // Don't parse as hex_float. allow_trailing_junk, junk_string_value_, read_as_double, diff --git a/double-conversion/double-conversion.h b/double-conversion/double-conversion.h index 1ccd7fcd..86cf25ea 100644 --- a/double-conversion/double-conversion.h +++ b/double-conversion/double-conversion.h @@ -396,6 +396,7 @@ class StringToDoubleConverter { ALLOW_TRAILING_SPACES = 16, ALLOW_SPACES_AFTER_SIGN = 32, ALLOW_CASE_INSENSIBILITY = 64, + ALLOW_HEX_FLOATS = 128, }; // Flags should be a bit-or combination of the possible Flags-enum. @@ -429,6 +430,11 @@ class StringToDoubleConverter { // StringToDouble("+ 123.2") -> 123.2 // - ALLOW_CASE_INSENSIBILITY: ignore case of characters for special values: // infinity and nan. + // - ALLOW_HEX_FLOATS: allows hexadecimal float literals. + // This *must* start with "0x" and separate the exponent with "p". + // Examples: 0x1.2p3 == 9.0 + // 0x10.1p0 == 16.0625 + // ALLOW_HEX and ALLOW_HEX_FLOATS are indendent. // // empty_string_value is returned when an empty string is given as input. // If ALLOW_LEADING_SPACES or ALLOW_TRAILING_SPACES are set, then a string diff --git a/test/cctest/test-conversions.cc b/test/cctest/test-conversions.cc index d70624d9..cf556e6c 100644 --- a/test/cctest/test-conversions.cc +++ b/test/cctest/test-conversions.cc @@ -2563,6 +2563,91 @@ TEST(StringToDoubleHexString) { CHECK_EQ(295147905179352960000.0, StrToD("0x100000000000018000", flags, 0.0, &processed, &all_used)); CHECK(all_used); + + flags = StringToDoubleConverter::ALLOW_HEX_FLOATS; + + CHECK_EQ(3.0, StrToD("0x3p0", flags, 0.0, &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(0.0, StrToD("0x.0p0", flags, 0.0, &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(3.0, StrToD("0x3.0p0", flags, 0.0, &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(3.0, StrToD("0x3.p0", flags, 0.0, &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(-5.634002666912405e+27, StrToD("-0x123456789012345678901234p0", + flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(72057594037927940.0, StrToD("0x100000000000001p0", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(72057594037927940.0, StrToD("0x100000000000000p0", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(295147905179352830000.0, StrToD("0x100000000000000001p0", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(295147905179352830000.0, StrToD("0x100000000000000000p0", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(295147905179352900000.0, StrToD("0x100000000000008001p0", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(295147905179352830000.0, StrToD("0x100000000000008000p0", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(295147905179352960000.0, StrToD("0x100000000000018001p0", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(295147905179352960000.0, StrToD("0x100000000000018000p0", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(4.722366482869645e+21, StrToD("0x100000000000000001p4", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(4.722366482869645e+21, StrToD("0x100000000000000000p+4", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(4.722366482869646e+21, StrToD("0x100000000000008001p04", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(18446744073709552000.0, StrToD("0x100000000000008000p-4", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(18446744073709560000.0, StrToD("0x100000000000018001p-04", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(4.722366482869647e+21, StrToD("0x100000000000018000p4", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(Double::Infinity(), StrToD("0x1p2000", flags, 0.0, + &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(0.0, StrToD("0x1p-2000", flags, 0.0, &processed, &all_used)); + CHECK(all_used); + + CHECK_EQ(-0.0, StrToD("-0x1p-2000", flags, 0.0, &processed, &all_used)); + CHECK(all_used); } @@ -3789,13 +3874,30 @@ TEST(StringToFloatHexString) { CHECK_EQ(5.0f, StrToF(" + 0x5 ", flags, 0.0f, &processed, &all_used)); CHECK(all_used); - CHECK_EQ(Single::NaN(), StrToF("- -0x5", flags, 0.0f, &processed, &all_used)); + CHECK_EQ(Single::NaN(), StrToF("- -0x5", flags, 0.0f, + &processed, &all_used)); CHECK_EQ(0, processed); - CHECK_EQ(Single::NaN(), StrToF("- +0x5", flags, 0.0f, &processed, &all_used)); + CHECK_EQ(Single::NaN(), StrToF("- +0x5", flags, 0.0f, + &processed, &all_used)); CHECK_EQ(0, processed); - CHECK_EQ(Single::NaN(), StrToF("+ +0x5", flags, 0.0f, &processed, &all_used)); + CHECK_EQ(Single::NaN(), StrToF("+ +0x5", flags, 0.0f, + &processed, &all_used)); + CHECK_EQ(0, processed); + + CHECK_EQ(Single::NaN(), StrToF("0x3p0", flags, 0.0f, &processed, &all_used)); + CHECK_EQ(0, processed); + + CHECK_EQ(Single::NaN(), StrToF("0x.0p0", flags, 0.0f, &processed, &all_used)); + CHECK_EQ(0, processed); + + CHECK_EQ(Single::NaN(), StrToF("0x3.0p0", flags, 0.0f, + &processed, &all_used)); + CHECK_EQ(0, processed); + + CHECK_EQ(Single::NaN(), StrToF("0x3.p0", flags, 0.0f, + &processed, &all_used)); CHECK_EQ(0, processed); flags = StringToDoubleConverter::ALLOW_HEX; @@ -3892,6 +3994,20 @@ TEST(StringToFloatHexString) { CHECK_EQ(Single::NaN(), StrToF("+ +0x5", flags, 0.0f, &processed, &all_used)); CHECK_EQ(0, processed); + CHECK_EQ(Single::NaN(), StrToF("0x3p0", flags, 0.0f, &processed, &all_used)); + CHECK_EQ(0, processed); + + CHECK_EQ(Single::NaN(), StrToF("0x.0p0", flags, 0.0f, &processed, &all_used)); + CHECK_EQ(0, processed); + + CHECK_EQ(Single::NaN(), StrToF("0x3.0p0", flags, 0.0f, + &processed, &all_used)); + CHECK_EQ(0, processed); + + CHECK_EQ(Single::NaN(), StrToF("0x3.p0", flags, 0.0f, + &processed, &all_used)); + CHECK_EQ(0, processed); + flags = StringToDoubleConverter::ALLOW_TRAILING_JUNK | StringToDoubleConverter::ALLOW_HEX; @@ -4014,6 +4130,19 @@ TEST(StringToFloatHexString) { CHECK_EQ(Single::NaN(), StrToF("+ +0x5", flags, 0.0f, &processed, &all_used)); CHECK_EQ(0, processed); + CHECK_EQ(3.0f, StrToF("0x3p0", flags, 0.0f, &processed, &all_used)); + CHECK_EQ(3, processed); + + CHECK_EQ(Single::NaN(), StrToF("0x.0p0", flags, 0.0f, &processed, &all_used)); + CHECK_EQ(0, processed); + + CHECK_EQ(3.0f, StrToF("0x3.0p0", flags, 0.0f, &processed, &all_used)); + CHECK_EQ(3, processed); + + CHECK_EQ(3.0f, StrToF("0x3.p0", flags, 0.0f, &processed, &all_used)); + CHECK_EQ(3, processed); + + flags = StringToDoubleConverter::ALLOW_TRAILING_JUNK | StringToDoubleConverter::ALLOW_LEADING_SPACES | StringToDoubleConverter::ALLOW_TRAILING_SPACES |