mirror of
https://github.com/fastfloat/fast_float.git
synced 2026-11-10 07:09:53 +08:00
Add chars_format::javascript_sloppy (Annex B NonOctalDecimalIntegerLiteral)
chars_format::javascript follows the strict-mode grammar, which rejects
any leading zero. In sloppy mode, JavaScript also accepts a leading zero
when one of the digits is 8 or 9 ("08.5" is 8.5, Annex B's
NonOctalDecimalIntegerLiteral). A leading zero followed only by octal
digits is a LegacyOctalIntegerLiteral ("0775"), which is not a decimal
number: it is rejected with the new parse_error::legacy_octal_integer_part
so the caller can parse it in base 8.
javascript_sloppy sets one extra bit on top of javascript, and the check
lives inside the existing runtime JavaScript branch of
parse_number_string<false>, so the general path is unchanged. An
interleaved A/B with realbenchmark on canada.txt and mesh.txt (ASCII and
UTF-16, 32/64-bit) shows identical instructions per float and cycles
within noise.
Document both formats in the README.
This commit is contained in:
parent
e0e355c073
commit
3254622f74
22
README.md
22
README.md
@ -418,6 +418,28 @@ int main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`fast_float::chars_format::javascript` follows the strict-mode grammar. In sloppy
|
||||||
|
mode, JavaScript also accepts a leading zero when one of the digits is 8 or 9
|
||||||
|
(`08.5` is 8.5, a `NonOctalDecimalIntegerLiteral`), which you can enable with
|
||||||
|
`fast_float::chars_format::javascript_sloppy`. A leading zero followed only by
|
||||||
|
octal digits (`0775`) is a legacy octal literal, not a decimal number: it is
|
||||||
|
rejected with `fast_float::parse_error::legacy_octal_integer_part`, and you may
|
||||||
|
parse it in base 8 instead.
|
||||||
|
|
||||||
|
```C++
|
||||||
|
#include "fast_float/fast_float.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::string input = "08.5"; // valid in sloppy-mode JavaScript
|
||||||
|
double result;
|
||||||
|
fast_float::parse_options options{fast_float::chars_format::javascript_sloppy};
|
||||||
|
auto answer = fast_float::from_chars_advanced(input.data(), input.data() + input.size(), result, options);
|
||||||
|
if (answer.ec != std::errc() || result != 8.5) { std::cerr << "should have parsed 8.5\n"; return EXIT_FAILURE; }
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Multiplication of an integer by a power of 10
|
## Multiplication of an integer by a power of 10
|
||||||
An integer `W` can be multiplied by a power of ten `10^Q` and
|
An integer `W` can be multiplied by a power of ten `10^Q` and
|
||||||
converted to `double` with correctly rounded value
|
converted to `double` with correctly rounded value
|
||||||
|
|||||||
@ -298,6 +298,10 @@ enum class parse_error {
|
|||||||
no_digits_in_mantissa,
|
no_digits_in_mantissa,
|
||||||
// Scientific notation requires an exponential part.
|
// Scientific notation requires an exponential part.
|
||||||
missing_exponential_part,
|
missing_exponential_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,
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename UC> struct parsed_number_string_t {
|
template <typename UC> struct parsed_number_string_t {
|
||||||
@ -425,8 +429,23 @@ parse_number_string(UC const *p, UC const *pend, parse_options_t<UC> options,
|
|||||||
// 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 ".".
|
||||||
if ((digit_count > 1) && (start_digits[0] == UC('0'))) {
|
if ((digit_count > 1) && (start_digits[0] == UC('0'))) {
|
||||||
return report_parse_error<UC>(start_digits,
|
if (!uint64_t(fmt & detail::basic_javascript_sloppy_fmt)) {
|
||||||
parse_error::leading_zeros_in_integer_part);
|
return report_parse_error<UC>(
|
||||||
|
start_digits, parse_error::leading_zeros_in_integer_part);
|
||||||
|
}
|
||||||
|
// Sloppy mode (Annex B): a NonOctalDecimalIntegerLiteral has a leading
|
||||||
|
// zero and at least one digit that is 8 or 9 ("08.5" is 8.5). With
|
||||||
|
// octal digits only, it is a LegacyOctalIntegerLiteral ("0775"), which
|
||||||
|
// is not a decimal number: report it so the caller can parse it in
|
||||||
|
// base 8.
|
||||||
|
bool has_non_octal_digit = false;
|
||||||
|
for (UC const *q = start_digits; q != end_of_integer_part; ++q) {
|
||||||
|
has_non_octal_digit |= (*q >= UC('8'));
|
||||||
|
}
|
||||||
|
if (!has_non_octal_digit) {
|
||||||
|
return report_parse_error<UC>(start_digits,
|
||||||
|
parse_error::legacy_octal_integer_part);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -40,6 +40,7 @@ namespace detail {
|
|||||||
constexpr chars_format basic_json_fmt = chars_format(1 << 5);
|
constexpr chars_format basic_json_fmt = chars_format(1 << 5);
|
||||||
constexpr chars_format basic_fortran_fmt = chars_format(1 << 6);
|
constexpr chars_format basic_fortran_fmt = chars_format(1 << 6);
|
||||||
constexpr chars_format basic_javascript_fmt = chars_format(1 << 9);
|
constexpr chars_format basic_javascript_fmt = chars_format(1 << 9);
|
||||||
|
constexpr chars_format basic_javascript_sloppy_fmt = chars_format(1 << 10);
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
enum class chars_format : uint64_t {
|
enum class chars_format : uint64_t {
|
||||||
@ -55,7 +56,7 @@ enum class chars_format : uint64_t {
|
|||||||
general = fixed | scientific,
|
general = fixed | scientific,
|
||||||
allow_leading_plus = 1 << 7,
|
allow_leading_plus = 1 << 7,
|
||||||
skip_white_space = 1 << 8,
|
skip_white_space = 1 << 8,
|
||||||
// ECMAScript DecimalLiteral:
|
// ECMAScript DecimalLiteral (strict mode):
|
||||||
// https://tc39.es/ecma262/#prod-DecimalLiteral
|
// https://tc39.es/ecma262/#prod-DecimalLiteral
|
||||||
// Like JSON, the integer part must not have leading zeros ("01" is
|
// Like JSON, the integer part must not have leading zeros ("01" is
|
||||||
// rejected), but unlike JSON the integer part may be empty (".5") and the
|
// rejected), but unlike JSON the integer part may be empty (".5") and the
|
||||||
@ -63,6 +64,16 @@ enum class chars_format : uint64_t {
|
|||||||
// accepted, and a leading plus sign with allow_leading_plus.
|
// accepted, and a leading plus sign with allow_leading_plus.
|
||||||
javascript =
|
javascript =
|
||||||
uint64_t(detail::basic_javascript_fmt) | fixed | scientific | no_infnan,
|
uint64_t(detail::basic_javascript_fmt) | fixed | scientific | no_infnan,
|
||||||
|
// ECMAScript DecimalLiteral in sloppy mode, which adds Annex B's
|
||||||
|
// NonOctalDecimalIntegerLiteral: a leading zero is accepted when one of
|
||||||
|
// the digits is 8 or 9 ("08.5" is 8.5). A leading zero followed only by
|
||||||
|
// octal digits is a LegacyOctalIntegerLiteral ("0775"): it is not a
|
||||||
|
// decimal number, so it is rejected with legacy_octal_integer_part and the
|
||||||
|
// caller may parse it in base 8.
|
||||||
|
// https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
|
||||||
|
javascript_sloppy = uint64_t(detail::basic_javascript_fmt) |
|
||||||
|
uint64_t(detail::basic_javascript_sloppy_fmt) | fixed |
|
||||||
|
scientific | no_infnan,
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename UC> struct from_chars_result_t {
|
template <typename UC> struct from_chars_result_t {
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
* Exercise the JavaScript (ECMAScript DecimalLiteral) conversion option.
|
* Exercise the JavaScript (ECMAScript DecimalLiteral) conversion options,
|
||||||
|
* strict (chars_format::javascript) and sloppy
|
||||||
|
* (chars_format::javascript_sloppy).
|
||||||
* https://tc39.es/ecma262/#prod-DecimalLiteral
|
* https://tc39.es/ecma262/#prod-DecimalLiteral
|
||||||
|
* https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
|
||||||
*/
|
*/
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -172,6 +175,120 @@ int main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sloppy mode accepts everything strict mode accepts...
|
||||||
|
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::javascript_sloppy);
|
||||||
|
if (answer.ec != std::errc() || result != expected.value ||
|
||||||
|
std::string(answer.ptr) != expected.junk_chars) {
|
||||||
|
std::cerr << "javascript_sloppy fmt failed on valid javascript " << s
|
||||||
|
<< std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... plus NonOctalDecimalIntegerLiteral: a leading zero followed by digits,
|
||||||
|
// at least one of which is 8 or 9, is a decimal integer part.
|
||||||
|
std::vector<AcceptedValue> const accept_sloppy{
|
||||||
|
{"08", {8., ""}}, {"09", {9., ""}}, {"-08", {-8., ""}},
|
||||||
|
{"0888", {888., ""}}, {"0778", {778., ""}}, {"08.5", {8.5, ""}},
|
||||||
|
{"-08.5", {-8.5, ""}}, {"08.", {8., ""}}, {"0888e-1", {88.8, ""}},
|
||||||
|
{"09e+1", {90., ""}}, {"08.5e1", {85., ""}}, {"0009", {9., ""}},
|
||||||
|
{"08n", {8., "n"}},
|
||||||
|
};
|
||||||
|
for (std::size_t i = 0; i < accept_sloppy.size(); ++i) {
|
||||||
|
auto const &s = accept_sloppy[i].input;
|
||||||
|
auto const &expected = accept_sloppy[i].expected;
|
||||||
|
double result;
|
||||||
|
auto answer =
|
||||||
|
fast_float::from_chars(s.data(), s.data() + s.size(), result,
|
||||||
|
fast_float::chars_format::javascript_sloppy);
|
||||||
|
if (answer.ec != std::errc()) {
|
||||||
|
std::cerr << "javascript_sloppy fmt rejected valid sloppy javascript "
|
||||||
|
<< s << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (result != expected.value) {
|
||||||
|
std::cerr << "javascript_sloppy 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 << "javascript_sloppy fmt has wrong trailing characters " << s
|
||||||
|
<< " (expected " << expected.junk_chars << " got " << answer.ptr
|
||||||
|
<< ")" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
// Strict mode rejects all of these as leading zeros.
|
||||||
|
auto strict = fast_float::parse_number_string<false>(
|
||||||
|
s.data(), s.data() + s.size(),
|
||||||
|
fast_float::parse_options(fast_float::chars_format::javascript));
|
||||||
|
if (strict.valid ||
|
||||||
|
strict.error !=
|
||||||
|
fast_float::parse_error::leading_zeros_in_integer_part) {
|
||||||
|
std::cerr << "javascript fmt should have rejected " << s
|
||||||
|
<< " for leading zeros" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A leading zero followed by octal digits only is a
|
||||||
|
// LegacyOctalIntegerLiteral, which is not a decimal number.
|
||||||
|
std::vector<RejectedValue> const reject_sloppy{
|
||||||
|
{"00", {fast_float::parse_error::legacy_octal_integer_part, 0}},
|
||||||
|
{"01", {fast_float::parse_error::legacy_octal_integer_part, 0}},
|
||||||
|
{"0775", {fast_float::parse_error::legacy_octal_integer_part, 0}},
|
||||||
|
{"-0775", {fast_float::parse_error::legacy_octal_integer_part, 1}},
|
||||||
|
{"00.5", {fast_float::parse_error::legacy_octal_integer_part, 0}},
|
||||||
|
{"00.0e-1", {fast_float::parse_error::legacy_octal_integer_part, 0}},
|
||||||
|
{"0775e-1", {fast_float::parse_error::legacy_octal_integer_part, 0}},
|
||||||
|
{"07.5", {fast_float::parse_error::legacy_octal_integer_part, 0}},
|
||||||
|
{"07n", {fast_float::parse_error::legacy_octal_integer_part, 0}},
|
||||||
|
// Unchanged from strict mode.
|
||||||
|
{".", {fast_float::parse_error::no_digits_in_mantissa, 1}},
|
||||||
|
{"-", {fast_float::parse_error::missing_integer_or_dot_after_sign, 1}},
|
||||||
|
{"+1", {fast_float::parse_error::no_digits_in_mantissa, 0}},
|
||||||
|
{"inf", {fast_float::parse_error::no_digits_in_mantissa, 0}},
|
||||||
|
};
|
||||||
|
for (std::size_t i = 0; i < reject_sloppy.size(); ++i) {
|
||||||
|
auto const &f = reject_sloppy[i].input;
|
||||||
|
auto const &expected_reason = reject_sloppy[i].reason;
|
||||||
|
double result;
|
||||||
|
auto answer =
|
||||||
|
fast_float::from_chars(f.data(), f.data() + f.size(), result,
|
||||||
|
fast_float::chars_format::javascript_sloppy);
|
||||||
|
if (answer.ec == std::errc()) {
|
||||||
|
std::cerr << "javascript_sloppy fmt accepted invalid javascript " << f
|
||||||
|
<< std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
auto parsed = fast_float::parse_number_string<false>(
|
||||||
|
f.data(), f.data() + f.size(),
|
||||||
|
fast_float::parse_options(fast_float::chars_format::javascript_sloppy));
|
||||||
|
if (parsed.valid) {
|
||||||
|
std::cerr << "javascript_sloppy parse accepted invalid javascript " << f
|
||||||
|
<< std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (parsed.error != expected_reason.error) {
|
||||||
|
std::cerr << "javascript_sloppy parse failure had invalid error reason "
|
||||||
|
<< f << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
intptr_t error_location = parsed.lastmatch - f.data();
|
||||||
|
if (error_location != expected_reason.location_offset) {
|
||||||
|
std::cerr << "javascript_sloppy parse failure had invalid error location "
|
||||||
|
<< f << " (expected " << expected_reason.location_offset
|
||||||
|
<< " got " << error_location << ")" << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// With allow_leading_plus, a leading plus sign is accepted (unlike JSON,
|
// With allow_leading_plus, a leading plus sign is accepted (unlike JSON,
|
||||||
// where it is ignored).
|
// where it is ignored).
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user