More optimized code especially if FASTFLOAT_ISNOT_CHECKED_BOUNDS or FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN defined.

Cleanup in many parsing parts.
Fix an error in JSON parsing.
This commit is contained in:
HedgehogInTheCPP 2026-09-19 20:29:11 +03:00
parent a4d1a08732
commit 5f6c3c8df9
2 changed files with 61 additions and 55 deletions

View File

@ -460,19 +460,19 @@ enum class parse_error : uint_fast8_t {
// Scientific notation requires an exponential part. // Scientific notation requires an exponential part.
missing_exponential_part, missing_exponential_part,
#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN #ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
// [JSON-only] The minus sign must be followed by an integer. // [JavaScript-only, sloppy mode] The integer part is a legacy octal literal
missing_integer_after_sign, // (a leading zero followed by octal digits only), which is not a decimal
// number. Parse it in base 8 instead.
legacy_octal_integer_part,
// [JSON/JavaScript-only] The integer part must not have leading zeros. // [JSON/JavaScript-only] The integer part must not have leading zeros.
leading_zeros_in_integer_part, leading_zeros_in_integer_part,
// [JSON-only] The minus sign must be followed by an integer.
missing_integer_after_sign,
// [JSON-only] The integer part must have at least one digit. // [JSON-only] The integer part must have at least one digit.
no_digits_in_integer_part, no_digits_in_integer_part,
// [JSON-only] If there is a decimal point, there must be digits in the // [JSON-only] If there is a decimal point, there must be digits in the
// fractional part. // fractional part.
no_digits_in_fractional_part, no_digits_in_fractional_part,
// [JavaScript-only, sloppy mode] The integer part is a legacy octal literal
// (a leading zero followed by octal digits only), which is not a decimal
// number. Parse it in base 8 instead.
legacy_octal_integer_part,
#endif #endif
}; };
@ -488,9 +488,10 @@ template <typename UC> struct parsed_number_string_t {
FASTFLOAT_NO_UNIQUE_ADDRESS bool invalid; FASTFLOAT_NO_UNIQUE_ADDRESS bool invalid;
FASTFLOAT_NO_UNIQUE_ADDRESS bool too_many_digits; FASTFLOAT_NO_UNIQUE_ADDRESS bool too_many_digits;
// contains the range of the significant digits // contains the range of the significant digits, fully optional, only matters
FASTFLOAT_NO_UNIQUE_ADDRESS span<UC const> integer; // non-nullable // if it's length > 0
FASTFLOAT_NO_UNIQUE_ADDRESS span<UC const> fraction; // nullable FASTFLOAT_NO_UNIQUE_ADDRESS span<UC const> integer;
FASTFLOAT_NO_UNIQUE_ADDRESS span<UC const> fraction;
}; };
using byte_span = span<char const>; using byte_span = span<char const>;
@ -525,23 +526,21 @@ parse_number_string(UC const *p, UC const *pend,
FASTFLOAT_ASSUME(p < pend); // so dereference without checks FASTFLOAT_ASSUME(p < pend); // so dereference without checks
#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN #ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
answer.negative = *p == UC('-'); answer.negative = *p == UC('-');
// C++17 20.19.3.(7.1) explicitly forbids '+' sign here
if (answer.negative || if (answer.negative ||
// C++17 20.19.3.(7.1) explicitly forbids '+' sign here (!json_fmt &&
(chars_format_t(options.format & chars_format::allow_leading_plus) && chars_format_t(options.format & chars_format::allow_leading_plus) &&
!json_fmt && *p == UC('+'))) { *p == UC('+'))) {
++p; ++p;
if (p == pend) {
return report_parse_error<UC>(
answer, p, parse_error::missing_integer_or_dot_after_sign);
}
if FASTFLOAT_CONSTEXPR17 (json_fmt) { if FASTFLOAT_CONSTEXPR17 (json_fmt) {
if (!is_integer(*p)) { // A sign must be followed by an integer if (p == pend &&
!is_integer(*p)) { // A sign must be followed by an integer
return report_parse_error<UC>(answer, p, return report_parse_error<UC>(answer, p,
parse_error::missing_integer_after_sign); parse_error::missing_integer_after_sign);
} }
} else { } else {
if (!is_integer(*p) && *p != options.decimal_point) { if (p == pend && !is_integer(*p) && *p != options.decimal_point) {
// A sign must be followed by an integer or the dot // A sign must be followed by an integer or the dot
return report_parse_error<UC>( return report_parse_error<UC>(
answer, p, parse_error::missing_integer_or_dot_after_sign); answer, p, parse_error::missing_integer_or_dot_after_sign);
@ -555,19 +554,20 @@ parse_number_string(UC const *p, UC const *pend,
// manual unroling because it's less branching and also it's allow more // manual unroling because it's less branching and also it's allow more
// inlining which is better. A a multiplication by 10 is cheaper than an // inlining which is better. A a multiplication by 10 is cheaper than an
// arbitrary integer multiplication. might overflow, handled later // arbitrary integer multiplication. might overflow, handled later
#ifdef FASTFLOAT_ISNOT_CHECKED_BOUNDS #if !defined(FASTFLOAT_ISNOT_CHECKED_BOUNDS) && \
!defined(FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN)
while (p != pend && is_integer(*p)) {
answer.mantissa = static_cast<fast_float::am_mant_t>(
answer.mantissa * 10 + static_cast<uint8_t>(*p - UC('0')));
++p;
}
#else
// External parser already check that this is num and it's exist // External parser already check that this is num and it's exist
do { do {
answer.mantissa = static_cast<fast_float::am_mant_t>( answer.mantissa = static_cast<fast_float::am_mant_t>(
answer.mantissa * 10 + static_cast<uint8_t>(*p - UC('0'))); answer.mantissa * 10 + static_cast<uint8_t>(*p - UC('0')));
++p; ++p;
} while (p != pend && is_integer(*p)); } while (p != pend && is_integer(*p));
#else
while (p != pend && is_integer(*p)) {
answer.mantissa = static_cast<fast_float::am_mant_t>(
answer.mantissa * 10 + static_cast<uint8_t>(*p - UC('0')));
++p;
}
#endif #endif
UC const *const end_of_integer_part = p; UC const *const end_of_integer_part = p;
auto digit_count = static_cast<am_digits>(end_of_integer_part - start_digits); auto digit_count = static_cast<am_digits>(end_of_integer_part - start_digits);
@ -583,12 +583,8 @@ parse_number_string(UC const *p, UC const *pend,
parse_error::no_digits_in_integer_part); parse_error::no_digits_in_integer_part);
} }
#endif #endif
if (start_digits[0] == UC('0') && digit_count > 1) { }
// But leading zero if (json_fmt || chars_format_t(options.format & detail::javascript_fmt)) {
return report_parse_error<UC>(answer, start_digits,
parse_error::leading_zeros_in_integer_part);
}
} else if (chars_format_t(options.format & detail::javascript_fmt)) {
// ECMAScript DecimalIntegerLiteral is "0" or a non-zero digit followed by // ECMAScript DecimalIntegerLiteral is "0" or a non-zero digit followed by
// digits: no leading zeros. Unlike JSON, the integer part may be empty // digits: no leading zeros. Unlike JSON, the integer part may be empty
// (".5"); the no_digits_in_mantissa check below still rejects ".". // (".5"); the no_digits_in_mantissa check below still rejects ".".
@ -597,17 +593,20 @@ parse_number_string(UC const *p, UC const *pend,
return report_parse_error<UC>( return report_parse_error<UC>(
answer, start_digits, parse_error::leading_zeros_in_integer_part); answer, start_digits, parse_error::leading_zeros_in_integer_part);
} }
// Sloppy mode (Annex B): a NonOctalDecimalIntegerLiteral has a leading if FASTFLOAT_CONSTEXPR17 (!json_fmt) {
// zero and at least one digit that is 8 or 9 ("08.5" is 8.5). With octal // Sloppy mode (Annex B): a NonOctalDecimalIntegerLiteral has a leading
// digits only, it is a LegacyOctalIntegerLiteral ("0775"), which is not a // zero and at least one digit that is 8 or 9 ("08.5" is 8.5). With
// decimal number: report it so the caller can parse it in base 8. // octal digits only, it is a LegacyOctalIntegerLiteral ("0775"), which
bool has_non_octal_digit = false; // is not a decimal number: report it so the caller can parse it in
for (UC const *q = start_digits; q != end_of_integer_part; ++q) { // base 8.
has_non_octal_digit |= (*q >= UC('8')); bool has_non_octal_digit = false;
} for (UC const *q = start_digits; q != end_of_integer_part; ++q) {
if (!has_non_octal_digit) { has_non_octal_digit |= (*q >= UC('8'));
return report_parse_error<UC>(answer, start_digits, }
parse_error::legacy_octal_integer_part); if (!has_non_octal_digit) {
return report_parse_error<UC>(answer, start_digits,
parse_error::legacy_octal_integer_part);
}
} }
} }
} }
@ -632,13 +631,13 @@ parse_number_string(UC const *p, UC const *pend,
#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN #ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
if FASTFLOAT_CONSTEXPR17 (json_fmt) { if FASTFLOAT_CONSTEXPR17 (json_fmt) {
// At least 1 digit in fractional part // At least 1 digit in fractional part
if (answer.exponent == 0) { if (has_decimal_point && answer.exponent == 0) {
return report_parse_error<UC>(answer, p, return report_parse_error<UC>(answer, p,
parse_error::no_digits_in_fractional_part); parse_error::no_digits_in_fractional_part);
} }
} } else
#endif #endif
else if (digit_count == 0) { // We must have encountered at least one integer! if (digit_count == 0) { // We must have encountered at least one integer!
return report_parse_error<UC>(answer, p, return report_parse_error<UC>(answer, p,
parse_error::no_digits_in_mantissa); parse_error::no_digits_in_mantissa);
} }
@ -744,16 +743,14 @@ parse_number_string(UC const *p, UC const *pend,
// pre-tokenized spans from above. // pre-tokenized spans from above.
answer.mantissa = 0; answer.mantissa = 0;
p = answer.integer.ptr; p = answer.integer.ptr;
UC const *int_end = p + answer.integer.len(); parse_digits_until_19(p, p + answer.integer.len(), answer.mantissa);
parse_digits_until_19(p, int_end, answer.mantissa);
if (answer.mantissa >= minimal_nineteen_digit_integer) { if (answer.mantissa >= minimal_nineteen_digit_integer) {
// We have a big integers, so skip the fraction part completely. // We have a big integers, so skip the fraction part completely.
answer.exponent = am_pow_t(end_of_integer_part - p) + exp_number; answer.exponent = am_pow_t(end_of_integer_part - p) + exp_number;
} else if (answer.fraction.len()) { } else if (answer.fraction.len()) {
// We have a value with a significant fractional component. // We have a value with a significant fractional component.
p = answer.fraction.ptr; p = answer.fraction.ptr;
UC const *const frac_end = p + answer.fraction.len(); parse_digits_until_19(p, p + answer.fraction.len(), answer.mantissa);
parse_digits_until_19(p, frac_end, answer.mantissa);
answer.exponent = am_pow_t(answer.fraction.ptr - p) + exp_number; answer.exponent = am_pow_t(answer.fraction.ptr - p) + exp_number;
} }
// We have now corrected both exponent and mantissa, to a truncated // We have now corrected both exponent and mantissa, to a truncated
@ -798,10 +795,21 @@ parse_int_string(UC const *p, UC const *pend, T &value,
auto const *const start_num = p; auto const *const start_num = p;
// Skip leading zeros // Skip leading zeros
#if !defined(FASTFLOAT_ISNOT_CHECKED_BOUNDS) && \
!defined(FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN)
while (p != pend && *p == UC('0')) { while (p != pend && *p == UC('0')) {
++p; ++p;
} }
#else
// External parser already check that this is num and it's exist
do {
if (*p == UC('0')) {
++p;
} else {
break;
}
} while (p != pend);
#endif
auto const has_leading_zeros = p > start_num; auto const has_leading_zeros = p > start_num;
auto const *const start_digits = p; auto const *const start_digits = p;
@ -809,7 +817,7 @@ parse_int_string(UC const *p, UC const *pend, T &value,
if (options.base == 10) { if (options.base == 10) {
auto const len = static_cast<am_digits>(pend - p); auto const len = static_cast<am_digits>(pend - p);
#ifndef FASTFLOAT_ISNOT_CHECKED_BOUNDS #ifndef FASTFLOAT_ISNOT_CHECKED_BOUNDS
// parser already check that this is num and it's exist // External parser already check that this is num and it's exist
if (len == 0) { if (len == 0) {
#endif #endif
if (has_leading_zeros) { if (has_leading_zeros) {
@ -845,7 +853,7 @@ parse_int_string(UC const *p, UC const *pend, T &value,
auto nd = static_cast<am_digits>(tz >> 3); auto nd = static_cast<am_digits>(tz >> 3);
nd = nd < len ? nd : len; nd = nd < len ? nd : len;
#ifndef FASTFLOAT_ISNOT_CHECKED_BOUNDS #ifndef FASTFLOAT_ISNOT_CHECKED_BOUNDS
// parser already check that this is num and it's exist // External parser already check that this is num and it's exist
if (nd == 0) { if (nd == 0) {
#endif #endif
if (has_leading_zeros) { if (has_leading_zeros) {
@ -862,7 +870,7 @@ parse_int_string(UC const *p, UC const *pend, T &value,
#endif #endif
if (nd > 3) { if (nd > 3) {
const UC *q = p + nd; const UC *q = p + nd;
auto rem = len - nd; am_digits rem = len - nd;
while (rem) { while (rem) {
if (*q < UC('0') || *q > UC('9')) { if (*q < UC('0') || *q > UC('9')) {
break; break;

View File

@ -534,7 +534,6 @@ template <typename T> struct span {
} }
}; };
/* alignas(16) - better data cache usage wo align */
struct value128 { struct value128 {
uint64_t low; uint64_t low;
uint64_t high; uint64_t high;
@ -713,7 +712,6 @@ full_multiplication(uint64_t a, uint64_t b) noexcept {
return answer; return answer;
} }
/* alignas(16) - better data cache usage without align */
struct adjusted_mantissa { struct adjusted_mantissa {
FASTFLOAT_NO_UNIQUE_ADDRESS am_mant_t mantissa; FASTFLOAT_NO_UNIQUE_ADDRESS am_mant_t mantissa;
FASTFLOAT_NO_UNIQUE_ADDRESS am_pow_t power2; FASTFLOAT_NO_UNIQUE_ADDRESS am_pow_t power2;