Skip to content

Commit

Permalink
Add support for hexadecimal float literals.
Browse files Browse the repository at this point in the history
Fixes #48.
  • Loading branch information
floitsch committed Sep 8, 2018
1 parent aa554d9 commit 05a3fea
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 11 deletions.
103 changes: 95 additions & 8 deletions double-conversion/double-conversion.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<class Iterator>
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(&current, 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 <int radix_log_2, class Iterator>
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;
Expand All @@ -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<char>(**current) - '0';
if (post_decimal) exponent -= radix_log_2;
} else if (IsCharacterDigitForRadix(**current, radix, 'a')) {
digit = static_cast<char>(**current) - 'a' + 10;
if (post_decimal) exponent -= radix_log_2;
} else if (IsCharacterDigitForRadix(**current, radix, 'A')) {
digit = static_cast<char>(**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;
Expand All @@ -612,17 +664,25 @@ static double RadixStringToIeee(Iterator* current,
int dropped_bits_mask = ((1 << overflow_bits_count) - 1);
int dropped_bits = static_cast<int>(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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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>(&current,
end,
sign,
parse_as_hex_float,
allow_trailing_junk,
junk_string_value_,
read_as_double,
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions double-conversion/double-conversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
135 changes: 132 additions & 3 deletions test/cctest/test-conversions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}


Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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 |
Expand Down

0 comments on commit 05a3fea

Please sign in to comment.