mirror of
https://github.com/fastfloat/fast_float.git
synced 2026-07-31 00:36:39 +08:00
Re-implements the optional digit-separator and base-prefix parsing (originally PR #369) on top of the current store_spans hot-path architecture, with the goal of zero overhead when the features are not used. Key changes vs. the original PR: - has_separator is now a *compile-time* template parameter on parse_number_string, dispatched once (from_chars_advanced -> parse_number_string_options) based on options.digit_separator. The has_separator==false instantiation that every default caller uses is byte-for-byte the separator-free parser: no separator comparison ever enters a digit loop and the SIMD eight-digit fast path is preserved. The separator-aware code lives only in the cold true instantiation. - The store_spans no-span hot path (added to main after the original PR was branched) is preserved. - parse_options_t fields are ordered so the two new single-byte fields (digit_separator, format_options) fall into the existing padding for UC == char. sizeof(parse_options_t<char>) stays 16 bytes, so the struct is still register-passed and the call boundary is unchanged. Result: for the default (no separator, no prefix) path, the generated assembly of from_chars<double> is identical to main, and benchmarks show no measurable regression (the original PR was 5-10% slower). Adds basictest coverage for digit separators (including the >19-digit overflow re-scan paths) and prefix skipping.
170 lines
5.7 KiB
C++
170 lines
5.7 KiB
C++
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <vector>
|
|
#include "fast_float/fast_float.h"
|
|
|
|
int main_readme() {
|
|
std::string const input = "+.1"; // not valid
|
|
double result;
|
|
fast_float::parse_options options{
|
|
fast_float::chars_format::json |
|
|
fast_float::chars_format::allow_leading_plus}; // should be ignored
|
|
auto answer = fast_float::from_chars_advanced(
|
|
input.data(), input.data() + input.size(), result, options);
|
|
if (answer.ec == std::errc()) {
|
|
std::cerr << "should have failed\n";
|
|
return EXIT_FAILURE;
|
|
}
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
int main_readme2() {
|
|
std::string const input = "inf"; // not valid in JSON
|
|
double result;
|
|
fast_float::parse_options options{
|
|
fast_float::chars_format::json |
|
|
fast_float::chars_format::allow_leading_plus}; // should be ignored
|
|
auto answer = fast_float::from_chars_advanced(
|
|
input.data(), input.data() + input.size(), result, options);
|
|
if (answer.ec == std::errc()) {
|
|
std::cerr << "should have failed\n";
|
|
return EXIT_FAILURE;
|
|
}
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
int main_readme3() {
|
|
std::string const input =
|
|
"inf"; // not valid in JSON but we allow it with json_or_infnan
|
|
double result;
|
|
fast_float::parse_options options{
|
|
fast_float::chars_format::json_or_infnan |
|
|
fast_float::chars_format::allow_leading_plus}; // should be ignored
|
|
auto answer = fast_float::from_chars_advanced(
|
|
input.data(), input.data() + input.size(), result, options);
|
|
if (answer.ec != std::errc() || (!std::isinf(result))) {
|
|
std::cerr << "should have parsed infinity\n";
|
|
return EXIT_FAILURE;
|
|
}
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
struct ExpectedResult {
|
|
double value;
|
|
std::string junk_chars;
|
|
};
|
|
|
|
struct AcceptedValue {
|
|
std::string input;
|
|
ExpectedResult expected;
|
|
};
|
|
|
|
struct RejectReason {
|
|
fast_float::parse_error error;
|
|
intptr_t location_offset;
|
|
};
|
|
|
|
struct RejectedValue {
|
|
std::string input;
|
|
RejectReason reason;
|
|
};
|
|
|
|
int main() {
|
|
std::vector<AcceptedValue> const accept{
|
|
{"-0.2", {-0.2, ""}},
|
|
{"0.02", {0.02, ""}},
|
|
{"0.002", {0.002, ""}},
|
|
{"1e+0000", {1., ""}},
|
|
{"0e-2", {0., ""}},
|
|
{"1e", {1., "e"}},
|
|
{"1e+", {1., "e+"}},
|
|
{"inf", {std::numeric_limits<double>::infinity(), ""}}};
|
|
std::vector<RejectedValue> const reject{
|
|
{"-.2", {fast_float::parse_error::missing_integer_after_sign, 1}},
|
|
{"00.02", {fast_float::parse_error::leading_zeros_in_integer_part, 0}},
|
|
{"0.e+1", {fast_float::parse_error::no_digits_in_fractional_part, 2}},
|
|
{"00.e+1", {fast_float::parse_error::leading_zeros_in_integer_part, 0}},
|
|
{".25", {fast_float::parse_error::no_digits_in_integer_part, 0}},
|
|
// The following cases already start as invalid JSON, so they are
|
|
// handled as trailing junk and the error is for not having digits in the
|
|
// empty string before the invalid token.
|
|
{"+0.25", {fast_float::parse_error::no_digits_in_integer_part, 0}},
|
|
{"inf", {fast_float::parse_error::no_digits_in_integer_part, 0}},
|
|
{"nan(snan)", {fast_float::parse_error::no_digits_in_integer_part, 0}}};
|
|
|
|
for (std::size_t i = 0; i < accept.size(); ++i) {
|
|
auto const &s = accept[i].input;
|
|
auto const &expected = accept[i].expected;
|
|
double result;
|
|
auto answer =
|
|
fast_float::from_chars(s.data(), s.data() + s.size(), result,
|
|
fast_float::chars_format::json_or_infnan);
|
|
if (answer.ec != std::errc()) {
|
|
std::cerr << "json fmt rejected valid json " << s << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (result != expected.value) {
|
|
std::cerr << "json fmt gave wrong result " << s << " (expected "
|
|
<< expected.value << " got " << result << ")" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (std::string(answer.ptr) != expected.junk_chars) {
|
|
std::cerr << "json fmt has wrong trailing characters " << s
|
|
<< " (expected " << expected.junk_chars << " got " << answer.ptr
|
|
<< ")" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
for (std::size_t i = 0; i < reject.size(); ++i) {
|
|
auto const &s = reject[i].input;
|
|
double result;
|
|
auto answer = fast_float::from_chars(s.data(), s.data() + s.size(), result,
|
|
fast_float::chars_format::json);
|
|
if (answer.ec == std::errc()) {
|
|
std::cerr << "json fmt accepted invalid json " << s << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
for (std::size_t i = 0; i < reject.size(); ++i) {
|
|
auto const &f = reject[i].input;
|
|
auto const &expected_reason = reject[i].reason;
|
|
auto answer = fast_float::parse_number_string<true, false>(
|
|
f.data(), f.data() + f.size(),
|
|
fast_float::parse_options(
|
|
fast_float::chars_format::json |
|
|
fast_float::chars_format::allow_leading_plus)); // should be ignored
|
|
if (answer.valid) {
|
|
std::cerr << "json parse accepted invalid json " << f << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (answer.error != expected_reason.error) {
|
|
std::cerr << "json parse failure had invalid error reason " << f
|
|
<< std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
intptr_t error_location = answer.lastmatch - f.data();
|
|
if (error_location != expected_reason.location_offset) {
|
|
std::cerr << "json parse failure had invalid error location " << f
|
|
<< " (expected " << expected_reason.location_offset << " got "
|
|
<< error_location << ")" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (main_readme() != EXIT_SUCCESS) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (main_readme2() != EXIT_SUCCESS) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
#ifndef __FAST_MATH__
|
|
if (main_readme3() != EXIT_SUCCESS) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
#endif
|
|
|
|
return EXIT_SUCCESS;
|
|
} |