mirror of
https://github.com/fastfloat/fast_float.git
synced 2026-10-28 18:07:16 +08:00
Merge pull request #407 from fastfloat/javascript-format
Add chars_format::javascript (ECMAScript DecimalLiteral)
This commit is contained in:
commit
e0e355c073
34
README.md
34
README.md
@ -384,6 +384,40 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
### You may also enforce the JavaScript format ([ECMAScript DecimalLiteral](https://tc39.es/ecma262/#prod-DecimalLiteral))
|
||||
|
||||
The JavaScript format is like the JSON format, except that the integer part may
|
||||
be empty (`.5`) and the fractional part may be empty (`5.`, `5.e3`). Leading
|
||||
zeros are still rejected (`01`), as are `inf` and `nan`.
|
||||
|
||||
```C++
|
||||
#include "fast_float/fast_float.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::string input = "01"; // not valid: leading zero
|
||||
double result;
|
||||
fast_float::parse_options options{fast_float::chars_format::javascript};
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
```C++
|
||||
#include "fast_float/fast_float.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::string input = ".5"; // valid in JavaScript, not in JSON
|
||||
double result;
|
||||
fast_float::parse_options options{fast_float::chars_format::javascript};
|
||||
auto answer = fast_float::from_chars_advanced(input.data(), input.data() + input.size(), result, options);
|
||||
if (answer.ec != std::errc() || result != 0.5) { std::cerr << "should have parsed 0.5\n"; return EXIT_FAILURE; }
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
```
|
||||
|
||||
## Multiplication of an integer by a power of 10
|
||||
An integer `W` can be multiplied by a power of ten `10^Q` and
|
||||
converted to `double` with correctly rounded value
|
||||
|
||||
@ -287,7 +287,7 @@ enum class parse_error {
|
||||
missing_integer_after_sign,
|
||||
// A sign must be followed by an integer or dot.
|
||||
missing_integer_or_dot_after_sign,
|
||||
// [JSON-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,
|
||||
// [JSON-only] The integer part must have at least one digit.
|
||||
no_digits_in_integer_part,
|
||||
@ -420,6 +420,15 @@ parse_number_string(UC const *p, UC const *pend, parse_options_t<UC> options,
|
||||
parse_error::leading_zeros_in_integer_part);
|
||||
}
|
||||
}
|
||||
else if (uint64_t(fmt & detail::basic_javascript_fmt)) {
|
||||
// ECMAScript DecimalIntegerLiteral is "0" or a non-zero digit followed by
|
||||
// digits: no leading zeros. Unlike JSON, the integer part may be empty
|
||||
// (".5"); the no_digits_in_mantissa check below still rejects ".".
|
||||
if ((digit_count > 1) && (start_digits[0] == UC('0'))) {
|
||||
return report_parse_error<UC>(start_digits,
|
||||
parse_error::leading_zeros_in_integer_part);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t exponent = 0;
|
||||
bool const has_decimal_point = (p != pend) && (*p == decimal_point);
|
||||
|
||||
@ -39,6 +39,7 @@ enum class chars_format : uint64_t;
|
||||
namespace detail {
|
||||
constexpr chars_format basic_json_fmt = chars_format(1 << 5);
|
||||
constexpr chars_format basic_fortran_fmt = chars_format(1 << 6);
|
||||
constexpr chars_format basic_javascript_fmt = chars_format(1 << 9);
|
||||
} // namespace detail
|
||||
|
||||
enum class chars_format : uint64_t {
|
||||
@ -54,6 +55,14 @@ enum class chars_format : uint64_t {
|
||||
general = fixed | scientific,
|
||||
allow_leading_plus = 1 << 7,
|
||||
skip_white_space = 1 << 8,
|
||||
// ECMAScript DecimalLiteral:
|
||||
// https://tc39.es/ecma262/#prod-DecimalLiteral
|
||||
// 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
|
||||
// fractional part may be empty ("5.", "5.e3"). A leading minus sign is
|
||||
// accepted, and a leading plus sign with allow_leading_plus.
|
||||
javascript =
|
||||
uint64_t(detail::basic_javascript_fmt) | fixed | scientific | no_infnan,
|
||||
};
|
||||
|
||||
template <typename UC> struct from_chars_result_t {
|
||||
@ -225,16 +234,12 @@ using parse_options = parse_options_t<char>;
|
||||
|
||||
#ifndef FASTFLOAT_ASSERT
|
||||
#define FASTFLOAT_ASSERT(x) \
|
||||
{ \
|
||||
static_cast<void>(x); \
|
||||
}
|
||||
{ static_cast<void>(x); }
|
||||
#endif
|
||||
|
||||
#ifndef FASTFLOAT_DEBUG_ASSERT
|
||||
#define FASTFLOAT_DEBUG_ASSERT(x) \
|
||||
{ \
|
||||
static_cast<void>(x); \
|
||||
}
|
||||
{ static_cast<void>(x); }
|
||||
#endif
|
||||
|
||||
// rust style `try!()` macro, or `?` operator
|
||||
|
||||
@ -61,6 +61,15 @@ cc_test(
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "javascript_fmt",
|
||||
srcs = ["javascript_fmt.cpp"],
|
||||
deps = [
|
||||
"//:fast_float",
|
||||
"@doctest//doctest",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "long_test",
|
||||
srcs = ["long_test.cpp"],
|
||||
|
||||
@ -85,6 +85,7 @@ fast_float_add_cpp_test(string_test)
|
||||
fast_float_add_cpp_test(fast_int)
|
||||
target_compile_features(fast_int PRIVATE cxx_std_17)
|
||||
fast_float_add_cpp_test(json_fmt)
|
||||
fast_float_add_cpp_test(javascript_fmt)
|
||||
fast_float_add_cpp_test(fortran)
|
||||
if(CMAKE_CXX_STANDARD GREATER_EQUAL 23)
|
||||
option(FASTFLOAT_FIXEDWIDTH_TESTS "Require fixed width test for C++23 (build will fail if the compiler won't support it)" ON)
|
||||
|
||||
213
tests/javascript_fmt.cpp
Normal file
213
tests/javascript_fmt.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Exercise the JavaScript (ECMAScript DecimalLiteral) conversion option.
|
||||
* https://tc39.es/ecma262/#prod-DecimalLiteral
|
||||
*/
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include "fast_float/fast_float.h"
|
||||
|
||||
int main_readme() {
|
||||
std::string const input = "01"; // not valid: leading zero
|
||||
double result;
|
||||
fast_float::parse_options options{fast_float::chars_format::javascript};
|
||||
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 = ".5"; // valid in JavaScript, not in JSON
|
||||
double result;
|
||||
fast_float::parse_options options{fast_float::chars_format::javascript};
|
||||
auto answer = fast_float::from_chars_advanced(
|
||||
input.data(), input.data() + input.size(), result, options);
|
||||
if (answer.ec != std::errc() || result != 0.5) {
|
||||
std::cerr << "should have parsed 0.5\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", {0., ""}},
|
||||
{"-0", {-0., ""}},
|
||||
{"0.5", {0.5, ""}},
|
||||
{"0.0", {0., ""}},
|
||||
{"0e5", {0., ""}},
|
||||
{"0e-2", {0., ""}},
|
||||
{"0.5e-1", {0.05, ""}},
|
||||
{"-0.2", {-0.2, ""}},
|
||||
{"0.02", {0.02, ""}},
|
||||
{"1e+0000", {1., ""}},
|
||||
{"123", {123., ""}},
|
||||
{"123.456", {123.456, ""}},
|
||||
{"123.456e7", {1234560000., ""}},
|
||||
{"1E5", {100000., ""}},
|
||||
// Unlike JSON, the integer part may be empty...
|
||||
{".5", {0.5, ""}},
|
||||
{"-.5", {-0.5, ""}},
|
||||
{".5e1", {5., ""}},
|
||||
{".5E-1", {0.05, ""}},
|
||||
// ... and so may the fractional part.
|
||||
{"5.", {5., ""}},
|
||||
{"-5.", {-5., ""}},
|
||||
{"5.e3", {5000., ""}},
|
||||
{"0.", {0., ""}},
|
||||
{"0.e1", {0., ""}},
|
||||
// An incomplete exponent is trailing junk, as in the other formats.
|
||||
{"1e", {1., "e"}},
|
||||
{"1e+", {1., "e+"}},
|
||||
{"5.e", {5., "e"}},
|
||||
// Trailing junk.
|
||||
{"0x1", {0., "x1"}},
|
||||
{"1n", {1., "n"}},
|
||||
{"1_000", {1., "_000"}},
|
||||
};
|
||||
std::vector<RejectedValue> const reject{
|
||||
{"00", {fast_float::parse_error::leading_zeros_in_integer_part, 0}},
|
||||
{"01", {fast_float::parse_error::leading_zeros_in_integer_part, 0}},
|
||||
{"007", {fast_float::parse_error::leading_zeros_in_integer_part, 0}},
|
||||
{"00.5", {fast_float::parse_error::leading_zeros_in_integer_part, 0}},
|
||||
{"00.0e-1", {fast_float::parse_error::leading_zeros_in_integer_part, 0}},
|
||||
{"0775e-1", {fast_float::parse_error::leading_zeros_in_integer_part, 0}},
|
||||
{"-01", {fast_float::parse_error::leading_zeros_in_integer_part, 1}},
|
||||
{".", {fast_float::parse_error::no_digits_in_mantissa, 1}},
|
||||
{".e5", {fast_float::parse_error::no_digits_in_mantissa, 1}},
|
||||
{"-.", {fast_float::parse_error::no_digits_in_mantissa, 2}},
|
||||
{"-", {fast_float::parse_error::missing_integer_or_dot_after_sign, 1}},
|
||||
{"-e5", {fast_float::parse_error::missing_integer_or_dot_after_sign, 1}},
|
||||
{"e5", {fast_float::parse_error::no_digits_in_mantissa, 0}},
|
||||
// A leading plus sign needs allow_leading_plus.
|
||||
{"+1", {fast_float::parse_error::no_digits_in_mantissa, 0}},
|
||||
// No inf/nan.
|
||||
{"inf", {fast_float::parse_error::no_digits_in_mantissa, 0}},
|
||||
{"Infinity", {fast_float::parse_error::no_digits_in_mantissa, 0}},
|
||||
{"nan", {fast_float::parse_error::no_digits_in_mantissa, 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::javascript);
|
||||
if (answer.ec != std::errc()) {
|
||||
std::cerr << "javascript fmt rejected valid javascript " << s
|
||||
<< std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (result != expected.value) {
|
||||
std::cerr << "javascript 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 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::javascript);
|
||||
if (answer.ec == std::errc()) {
|
||||
std::cerr << "javascript fmt accepted invalid javascript " << 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<false>(
|
||||
f.data(), f.data() + f.size(),
|
||||
fast_float::parse_options(fast_float::chars_format::javascript));
|
||||
if (answer.valid) {
|
||||
std::cerr << "javascript parse accepted invalid javascript " << f
|
||||
<< std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (answer.error != expected_reason.error) {
|
||||
std::cerr << "javascript 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 << "javascript 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,
|
||||
// where it is ignored).
|
||||
{
|
||||
std::vector<std::string> const plus{"+1", "+.5", "+5.", "+0.5e1"};
|
||||
std::vector<double> const expected{1., 0.5, 5., 5.};
|
||||
fast_float::parse_options const options{
|
||||
fast_float::chars_format::javascript |
|
||||
fast_float::chars_format::allow_leading_plus};
|
||||
for (std::size_t i = 0; i < plus.size(); ++i) {
|
||||
auto const &s = plus[i];
|
||||
double result;
|
||||
auto answer = fast_float::from_chars_advanced(
|
||||
s.data(), s.data() + s.size(), result, options);
|
||||
if (answer.ec != std::errc() || result != expected[i]) {
|
||||
std::cerr << "javascript fmt with allow_leading_plus failed on " << s
|
||||
<< std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
std::string const s = "+01";
|
||||
double result;
|
||||
auto answer = fast_float::from_chars_advanced(s.data(), s.data() + s.size(),
|
||||
result, options);
|
||||
if (answer.ec == std::errc()) {
|
||||
std::cerr << "javascript fmt with allow_leading_plus accepted " << s
|
||||
<< std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (main_readme() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (main_readme2() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user