mirror of
https://github.com/fastfloat/fast_float.git
synced 2026-10-29 02:17:15 +08:00
Cleanup for code near merged code: compiler generate much better code in any mode without manual unroling because it's less branching and also it's allow more inlining which is better.
Merge branch 'main' of https://github.com/fastfloat/fast_float
This commit is contained in:
commit
c8894d3722
@ -1,12 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
|
||||
project(fast_float VERSION 8.2.10 LANGUAGES CXX)
|
||||
|
||||
project(fast_float VERSION 8.3.0 LANGUAGES CXX)
|
||||
set(FASTFLOAT_CXX_STANDARD 11 CACHE STRING "the C++ standard to use for fastfloat")
|
||||
set(CMAKE_CXX_STANDARD ${FASTFLOAT_CXX_STANDARD})
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
option(FASTFLOAT_TEST "Enable tests" OFF)
|
||||
|
||||
if(FASTFLOAT_TEST)
|
||||
@ -17,7 +14,8 @@ else(FASTFLOAT_TEST)
|
||||
endif(FASTFLOAT_TEST)
|
||||
|
||||
option(FASTFLOAT_SANITIZE "Sanitize addresses" OFF)
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
if(FASTFLOAT_SANITIZE)
|
||||
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE)
|
||||
else()
|
||||
@ -34,6 +32,7 @@ endif()
|
||||
|
||||
add_library(fast_float INTERFACE)
|
||||
|
||||
|
||||
option(FASTFLOAT_BENCHMARKS "Enable benchmarks" OFF)
|
||||
if(FASTFLOAT_BENCHMARKS)
|
||||
add_subdirectory(benchmarks)
|
||||
@ -41,6 +40,7 @@ else(FASTFLOAT_BENCHMARKS)
|
||||
message(STATUS "Benchmarks are disabled. Set FASTFLOAT_BENCHMARKS to ON to build benchmarks (assumes C++17).")
|
||||
endif(FASTFLOAT_BENCHMARKS)
|
||||
|
||||
|
||||
add_library(FastFloat::fast_float ALIAS fast_float)
|
||||
target_include_directories(
|
||||
fast_float
|
||||
@ -49,33 +49,15 @@ target_include_directories(
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
)
|
||||
target_compile_features(fast_float INTERFACE cxx_std_11)
|
||||
|
||||
if(FASTFLOAT_SANITIZE)
|
||||
target_compile_options(
|
||||
fast_float
|
||||
INTERFACE
|
||||
-fsanitize=address
|
||||
-fno-omit-frame-pointer
|
||||
-fsanitize=undefined
|
||||
-fno-sanitize-recover=all
|
||||
)
|
||||
target_link_libraries(
|
||||
fast_float
|
||||
INTERFACE
|
||||
-fsanitize=address
|
||||
-fno-omit-frame-pointer
|
||||
-fsanitize=undefined
|
||||
-fno-sanitize-recover=all
|
||||
)
|
||||
if(CMAKE_COMPILER_IS_GNUCC)
|
||||
target_compile_options(fast_float INTERFACE -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=all)
|
||||
target_link_libraries(fast_float INTERFACE -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=all)
|
||||
if (CMAKE_COMPILER_IS_GNUCC)
|
||||
target_link_libraries(fast_float INTERFACE -fuse-ld=gold)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC"
|
||||
AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.10)
|
||||
target_compile_options(fast_float INTERFACE /permissive-)
|
||||
endif()
|
||||
target_compile_options(fast_float INTERFACE $<$<AND:$<CXX_COMPILER_ID:MSVC>,$<VERSION_GREATER_EQUAL:$<CXX_COMPILER_VERSION>,19.10>>:/permissive->)
|
||||
|
||||
if(FASTFLOAT_INSTALL)
|
||||
include(CMakePackageConfigHelpers)
|
||||
@ -84,58 +66,23 @@ if(FASTFLOAT_INSTALL)
|
||||
set(FASTFLOAT_PROJECT_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/module/FastFloatConfig.cmake")
|
||||
set(FASTFLOAT_CONFIG_INSTALL_DIR "${CMAKE_INSTALL_DATAROOTDIR}/cmake/FastFloat")
|
||||
|
||||
if(CMAKE_VERSION VERSION_LESS "3.14")
|
||||
write_basic_package_version_file(
|
||||
"${FASTFLOAT_VERSION_CONFIG}"
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY SameMajorVersion
|
||||
)
|
||||
if(${CMAKE_VERSION} VERSION_LESS "3.14")
|
||||
write_basic_package_version_file("${FASTFLOAT_VERSION_CONFIG}" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion)
|
||||
else()
|
||||
write_basic_package_version_file(
|
||||
"${FASTFLOAT_VERSION_CONFIG}"
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY SameMajorVersion
|
||||
ARCH_INDEPENDENT
|
||||
)
|
||||
write_basic_package_version_file("${FASTFLOAT_VERSION_CONFIG}" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ARCH_INDEPENDENT)
|
||||
endif()
|
||||
configure_package_config_file("cmake/config.cmake.in"
|
||||
"${FASTFLOAT_PROJECT_CONFIG}"
|
||||
INSTALL_DESTINATION "${FASTFLOAT_CONFIG_INSTALL_DIR}")
|
||||
|
||||
configure_package_config_file(
|
||||
"cmake/config.cmake.in"
|
||||
"${FASTFLOAT_PROJECT_CONFIG}"
|
||||
INSTALL_DESTINATION "${FASTFLOAT_CONFIG_INSTALL_DIR}"
|
||||
)
|
||||
install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/fast_float" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
install(FILES "${FASTFLOAT_PROJECT_CONFIG}" "${FASTFLOAT_VERSION_CONFIG}" DESTINATION "${FASTFLOAT_CONFIG_INSTALL_DIR}")
|
||||
install(EXPORT ${PROJECT_NAME}-targets NAMESPACE FastFloat:: DESTINATION "${FASTFLOAT_CONFIG_INSTALL_DIR}")
|
||||
|
||||
install(
|
||||
DIRECTORY
|
||||
"${PROJECT_SOURCE_DIR}/include/fast_float"
|
||||
DESTINATION
|
||||
"${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
)
|
||||
|
||||
install(
|
||||
FILES
|
||||
"${FASTFLOAT_PROJECT_CONFIG}"
|
||||
"${FASTFLOAT_VERSION_CONFIG}"
|
||||
DESTINATION
|
||||
"${FASTFLOAT_CONFIG_INSTALL_DIR}"
|
||||
)
|
||||
|
||||
install(
|
||||
EXPORT
|
||||
${PROJECT_NAME}-targets
|
||||
NAMESPACE
|
||||
FastFloat::
|
||||
DESTINATION
|
||||
"${FASTFLOAT_CONFIG_INSTALL_DIR}"
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
fast_float
|
||||
EXPORT
|
||||
${PROJECT_NAME}-targets
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
install(TARGETS fast_float
|
||||
EXPORT ${PROJECT_NAME}-targets
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
62
README.md
62
README.md
@ -500,6 +500,62 @@ 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;
|
||||
}
|
||||
```
|
||||
|
||||
`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
|
||||
An integer `W` can be multiplied by a power of ten `10^Q` and
|
||||
converted to `double` with correctly rounded value
|
||||
@ -647,7 +703,7 @@ sufficiently recent version of CMake (3.11 or better at least):
|
||||
FetchContent_Declare(
|
||||
fast_float
|
||||
GIT_REPOSITORY https://github.com/irainman/fast_float.git
|
||||
GIT_TAG tags/v8.2.10
|
||||
GIT_TAG tags/v8.3.0
|
||||
GIT_SHALLOW TRUE)
|
||||
|
||||
FetchContent_MakeAvailable(fast_float)
|
||||
@ -663,7 +719,7 @@ You may also use [CPM](https://github.com/cpm-cmake/CPM.cmake), like so:
|
||||
CPMAddPackage(
|
||||
NAME fast_float
|
||||
GITHUB_REPOSITORY "irainman/fast_float"
|
||||
GIT_TAG v8.2.10)
|
||||
GIT_TAG v8.3.0)
|
||||
```
|
||||
|
||||
## Using as single header
|
||||
@ -675,7 +731,7 @@ if desired as described in the command line help.
|
||||
|
||||
You may directly download automatically generated single-header files:
|
||||
|
||||
<https://github.com/irainman/fast_float/releases/download/v8.2.10/fast_float.h>
|
||||
<https://github.com/irainman/fast_float/releases/download/v8.3.0/fast_float.h>
|
||||
|
||||
## Benchmarking
|
||||
|
||||
|
||||
@ -288,7 +288,7 @@ parse_digits_until_19(char const *&p, char const *pend, am_mant_t &mantissa) {
|
||||
p += 8;
|
||||
}
|
||||
// If mantissa < 10^12, a 4-digit block is guaranteed < 10^18 - 1.
|
||||
while (std::distance(p, pend) >= 4 && mantissa < 1000000000000ULL) {
|
||||
if (std::distance(p, pend) >= 4 && mantissa < 1000000000000ULL) {
|
||||
auto const value = read_chars_to_unsigned<uint32_t>(p);
|
||||
mantissa = mantissa * 10000 + parse_4_digits(value);
|
||||
p += 4;
|
||||
@ -307,7 +307,7 @@ parse_digits_until_19(UC const *&p, UC const *pend,
|
||||
am_mant_t &mantissa) noexcept {
|
||||
do {
|
||||
mantissa = mantissa * 10 + static_cast<uint8_t>(*p - UC('0'));
|
||||
} while ((++p != pend) && (mantissa < minimal_nineteen_digit_integer));
|
||||
} while (++p != pend && mantissa < minimal_nineteen_digit_integer);
|
||||
}
|
||||
#else
|
||||
template <typename UC>
|
||||
@ -316,7 +316,7 @@ parse_digits_until_19(UC const *&p, UC const *pend,
|
||||
am_mant_t &mantissa) noexcept {
|
||||
do {
|
||||
mantissa = mantissa * 10 + static_cast<uint8_t>(*p - UC('0'));
|
||||
} while ((++p != pend) && (mantissa < minimal_nineteen_digit_integer));
|
||||
} while (++p != pend && mantissa < minimal_nineteen_digit_integer);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -375,7 +375,7 @@ parse_digits_until_19(UC const *&p, UC const *pend,
|
||||
am_mant_t &mantissa) noexcept {
|
||||
do {
|
||||
mantissa = mantissa * 10 + static_cast<uint8_t>(*p - UC('0'));
|
||||
} while ((++p != pend) && (mantissa < minimal_nineteen_digit_integer));
|
||||
} while (++p != pend && mantissa < minimal_nineteen_digit_integer);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -386,7 +386,7 @@ template <typename UC>
|
||||
template <typename UC, FASTFLOAT_ENABLE_IF(!has_simd_opt<UC>()) = 0>
|
||||
#endif
|
||||
// dummy for compiler
|
||||
bool simd_parse_if_8_digits(UC const *, uint64_t &) {
|
||||
constexpr bool simd_parse_if_8_digits(UC const *, uint64_t &) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -402,7 +402,7 @@ loop_parse_if_digits(UC const *&p, UC const *const pend, uint64_t &i) noexcept {
|
||||
}
|
||||
}
|
||||
// Finalizer
|
||||
while ((p != pend) && is_integer(*p)) {
|
||||
while (p != pend && is_integer(*p)) {
|
||||
i = i * 10 + static_cast<uint8_t>(*p - UC('0')); // may overflow, that's ok
|
||||
++p;
|
||||
}
|
||||
@ -445,7 +445,7 @@ loop_parse_if_digits(char const *&p, char const *const pend,
|
||||
}
|
||||
}
|
||||
// Finalizer
|
||||
while ((p != pend) && is_integer(*p)) {
|
||||
while (p != pend && is_integer(*p)) {
|
||||
i = i * 10 + static_cast<uint8_t>(*p - '0'); // may overflow, that's ok
|
||||
++p;
|
||||
}
|
||||
@ -462,13 +462,17 @@ enum class parse_error : uint_fast8_t {
|
||||
#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
|
||||
// [JSON-only] The minus sign must be followed by an integer.
|
||||
missing_integer_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,
|
||||
// [JSON-only] If there is a decimal point, there must be digits in the
|
||||
// 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
|
||||
};
|
||||
|
||||
@ -512,7 +516,7 @@ report_parse_error(parsed_number_string_t<UC> &answer, UC const *p,
|
||||
// spans (read only by the rare digit_comp slow path) are not materialized,
|
||||
// which keeps the fat parsed_number_string_t off the hot path. The caller
|
||||
// re-parses with store_spans=true if the slow path is actually reached.
|
||||
template <bool basic_json_fmt, typename UC>
|
||||
template <bool json_fmt, typename UC>
|
||||
fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t<UC>
|
||||
parse_number_string(UC const *p, UC const *pend,
|
||||
parse_options_t<UC> const options,
|
||||
@ -520,26 +524,26 @@ parse_number_string(UC const *p, UC const *pend,
|
||||
parsed_number_string_t<UC> answer{};
|
||||
FASTFLOAT_ASSUME(p < pend); // so dereference without checks
|
||||
#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
|
||||
answer.negative = (*p == UC('-'));
|
||||
answer.negative = *p == UC('-');
|
||||
if (answer.negative ||
|
||||
// C++17 20.19.3.(7.1) explicitly forbids '+' sign here
|
||||
((chars_format_t(options.format & chars_format::allow_leading_plus)) &&
|
||||
(!basic_json_fmt && *p == UC('+')))) {
|
||||
chars_format_t(options.format & chars_format::allow_leading_plus) &&
|
||||
!json_fmt && *p == UC('+')) {
|
||||
++p;
|
||||
if (p == pend) {
|
||||
return report_parse_error<UC>(
|
||||
answer, p, parse_error::missing_integer_or_dot_after_sign);
|
||||
}
|
||||
|
||||
if FASTFLOAT_CONSTEXPR17 (basic_json_fmt) {
|
||||
if FASTFLOAT_CONSTEXPR17 (json_fmt) {
|
||||
if (!is_integer(*p)) {
|
||||
// a sign must be followed by an integer
|
||||
// A sign must be followed by an integer
|
||||
return report_parse_error<UC>(answer, p,
|
||||
parse_error::missing_integer_after_sign);
|
||||
}
|
||||
} else {
|
||||
if (!is_integer(*p) && (*p != options.decimal_point)) {
|
||||
// a sign must be followed by an integer or the dot
|
||||
if (!is_integer(*p) && *p != options.decimal_point) {
|
||||
// A sign must be followed by an integer or the dot
|
||||
return report_parse_error<UC>(
|
||||
answer, p, parse_error::missing_integer_or_dot_after_sign);
|
||||
}
|
||||
@ -548,43 +552,22 @@ parse_number_string(UC const *p, UC const *pend,
|
||||
#endif
|
||||
auto const *const start_digits = p;
|
||||
|
||||
// Straight-line unroll of the integer-part scan: most integer parts are
|
||||
// 1-5 digits, so peeling the first iterations eliminates the loop back-edge
|
||||
// for the common case. Semantics are identical to the original `while` loop:
|
||||
// i = 10*i + digit, advancing p: a multiplication by 10 is cheaper than an
|
||||
// HedgehogInTheCPP: compiler generate much better code in any mode without
|
||||
// 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
|
||||
// arbitrary integer multiplication. might overflow, handled later
|
||||
#ifndef FASTFLOAT_ISNOT_CHECKED_BOUNDS
|
||||
// parser already check that this is num and it's exist
|
||||
if ((p != pend) && is_integer(*p)) {
|
||||
#endif
|
||||
answer.mantissa = static_cast<uint8_t>(*p - UC('0'));
|
||||
#ifdef FASTFLOAT_ISNOT_CHECKED_BOUNDS
|
||||
// External parser already check that this is num and it's exist
|
||||
do {
|
||||
answer.mantissa = static_cast<fast_float::am_mant_t>(
|
||||
answer.mantissa * 10 + static_cast<uint8_t>(*p - UC('0')));
|
||||
++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;
|
||||
if ((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;
|
||||
if ((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;
|
||||
if ((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;
|
||||
if ((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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifndef FASTFLOAT_ISNOT_CHECKED_BOUNDS
|
||||
}
|
||||
#endif
|
||||
UC const *const end_of_integer_part = p;
|
||||
@ -593,21 +576,45 @@ parse_number_string(UC const *p, UC const *pend,
|
||||
answer.integer = span<UC const>(start_digits, digit_count);
|
||||
}
|
||||
#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
|
||||
if FASTFLOAT_CONSTEXPR17 (basic_json_fmt) {
|
||||
// at least 1 digit in integer part, without leading zeros
|
||||
if FASTFLOAT_CONSTEXPR17 (json_fmt) {
|
||||
#ifndef FASTFLOAT_ISNOT_CHECKED_BOUNDS
|
||||
// At least 1 digit in integer part
|
||||
if (digit_count == 0) {
|
||||
return report_parse_error<UC>(answer, p,
|
||||
parse_error::no_digits_in_integer_part);
|
||||
}
|
||||
if ((start_digits[0] == UC('0') && digit_count > 1)) {
|
||||
#endif
|
||||
if (start_digits[0] == UC('0') && digit_count > 1) {
|
||||
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
|
||||
// digits: no leading zeros. Unlike JSON, the integer part may be empty
|
||||
// (".5"); the no_digits_in_mantissa check below still rejects ".".
|
||||
if (start_digits[0] == UC('0') && digit_count > 1) {
|
||||
if (!chars_format_t(options.format & detail::javascript_sloppy_fmt)) {
|
||||
return report_parse_error<UC>(
|
||||
answer, 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>(answer, start_digits,
|
||||
parse_error::legacy_octal_integer_part);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// We can now parse the fraction part of the mantissa.
|
||||
bool const has_decimal_point = (p != pend) && (*p == options.decimal_point);
|
||||
bool const has_decimal_point = p != pend && *p == options.decimal_point;
|
||||
if (has_decimal_point) {
|
||||
++p;
|
||||
auto const *const before = p;
|
||||
@ -622,7 +629,7 @@ parse_number_string(UC const *p, UC const *pend,
|
||||
}
|
||||
digit_count -= static_cast<am_digits>(answer.exponent);
|
||||
#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
|
||||
if FASTFLOAT_CONSTEXPR17 (basic_json_fmt) {
|
||||
if FASTFLOAT_CONSTEXPR17 (json_fmt) {
|
||||
// at least 1 digit in fractional part
|
||||
if (answer.exponent == 0) {
|
||||
return report_parse_error<UC>(
|
||||
@ -632,7 +639,7 @@ parse_number_string(UC const *p, UC const *pend,
|
||||
#endif
|
||||
}
|
||||
#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
|
||||
else if (digit_count == 0) {
|
||||
// We must have encountered at least one integer!
|
||||
return report_parse_error<UC>(answer, p,
|
||||
@ -643,24 +650,18 @@ parse_number_string(UC const *p, UC const *pend,
|
||||
|
||||
// Now we can parse the explicit exponential part.
|
||||
am_pow_t exp_number = 0; // explicit exponential part
|
||||
if ((p != pend) &&
|
||||
((chars_format_t(options.format & chars_format::scientific) &&
|
||||
(UC('e') == *p || UC('E') == *p))
|
||||
if (p != pend && (chars_format_t(options.format & chars_format::scientific) &&
|
||||
(UC('e') == *p || UC('E') == *p))
|
||||
#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
|
||||
|| (chars_format_t(options.format & detail::basic_fortran_fmt) &&
|
||||
((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) ||
|
||||
(UC('D') == *p)))
|
||||
|| (chars_format_t(options.format & detail::fortran_fmt) &&
|
||||
(UC('+') == *p || UC('-') == *p || UC('d') == *p || UC('D') == *p))
|
||||
#endif
|
||||
)) {
|
||||
) {
|
||||
auto const *location_of_e = p;
|
||||
#ifdef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
|
||||
++p;
|
||||
#else
|
||||
if ((UC('e') == *p) || (UC('E') == *p)
|
||||
#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
|
||||
|| (UC('d') == *p) || (UC('D') == *p)
|
||||
#endif
|
||||
) {
|
||||
if (UC('e') == *p || UC('E') == *p || UC('d') == *p || UC('D') == *p) {
|
||||
++p;
|
||||
}
|
||||
#endif
|
||||
@ -675,8 +676,8 @@ parse_number_string(UC const *p, UC const *pend,
|
||||
}
|
||||
}
|
||||
// We have now parsed the sign of the exponent.
|
||||
if ((p == pend) || !is_integer(*p)) {
|
||||
if (!(chars_format_t(options.format & chars_format::fixed))) {
|
||||
if (p == pend || !is_integer(*p)) {
|
||||
if (!chars_format_t(options.format & chars_format::fixed)) {
|
||||
// The exponential part is invalid for scientific notation, so it
|
||||
// must be a trailing token for fixed notation. However, fixed
|
||||
// notation is disabled, so report a scientific notation error.
|
||||
@ -687,7 +688,7 @@ parse_number_string(UC const *p, UC const *pend,
|
||||
p = location_of_e;
|
||||
} else {
|
||||
// Now let's parse the explicit exponent.
|
||||
while ((p != pend) && is_integer(*p)) {
|
||||
while (p != pend && is_integer(*p)) {
|
||||
if (exp_number < am_bias_limit) {
|
||||
// check for exponent overflow if we have too many digits.
|
||||
auto const digit = static_cast<uint8_t>(*p - UC('0'));
|
||||
@ -702,8 +703,8 @@ parse_number_string(UC const *p, UC const *pend,
|
||||
}
|
||||
} else {
|
||||
// If it scientific and not fixed, we have to bail out.
|
||||
if ((chars_format_t(options.format & chars_format::scientific)) &&
|
||||
!(chars_format_t(options.format & chars_format::fixed))) {
|
||||
if (chars_format_t(options.format & chars_format::scientific) &&
|
||||
!chars_format_t(options.format & chars_format::fixed)) {
|
||||
return report_parse_error<UC>(answer, p,
|
||||
parse_error::missing_exponential_part);
|
||||
}
|
||||
@ -866,8 +867,9 @@ parse_int_string(UC const *p, UC const *pend, T &value,
|
||||
const UC *q = p + nd;
|
||||
auto rem = len - nd;
|
||||
while (rem) {
|
||||
if (*q < UC('0') || *q > UC('9'))
|
||||
if (*q < UC('0') || *q > UC('9')) {
|
||||
break;
|
||||
}
|
||||
++q;
|
||||
--rem;
|
||||
}
|
||||
|
||||
@ -300,18 +300,18 @@ parse_mantissa(bigint &result, const parsed_number_string_t<UC> &num) noexcept {
|
||||
#if defined(FASTFLOAT_64BIT_LIMB) && defined(FASTFLOAT_X86_SIMD) && \
|
||||
FASTFLOAT_X86_SIMD >= 31
|
||||
if FASTFLOAT_CONSTEXPR17 (sizeof(UC) == 1) {
|
||||
if (!is_constant_evaluated() && (std::distance(p, pend) >= 16) &&
|
||||
(step - counter >= 16) && (max_digits - digits >= 16)) {
|
||||
if (!is_constant_evaluated() std::distance(p, pend) >= 16 &&
|
||||
step - counter >= 16 && max_digits - digits >= 16) {
|
||||
parse_sixteen_digits(p, value, counter, digits);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
while ((std::distance(p, pend) >= 8) && (step - counter >= 8) &&
|
||||
(max_digits - digits >= 8)) {
|
||||
while (std::distance(p, pend) >= 8 && step - counter >= 8 &&
|
||||
max_digits - digits >= 8) {
|
||||
parse_eight_digits(p, value, counter, digits);
|
||||
}
|
||||
while ((std::distance(p, pend) >= 4) && (step - counter >= 4) &&
|
||||
(max_digits - digits >= 4)) {
|
||||
if (std::distance(p, pend) >= 4 && step - counter >= 4 &&
|
||||
max_digits - digits >= 4) {
|
||||
parse_four_digits(p, value, counter, digits);
|
||||
}
|
||||
while (counter < step && p != pend && digits < max_digits) {
|
||||
@ -350,14 +350,14 @@ parse_mantissa(bigint &result, const parsed_number_string_t<UC> &num) noexcept {
|
||||
#if defined(FASTFLOAT_64BIT_LIMB) && defined(FASTFLOAT_X86_SIMD) && \
|
||||
FASTFLOAT_X86_SIMD >= 31
|
||||
if FASTFLOAT_CONSTEXPR17 (sizeof(UC) == 1) {
|
||||
if (!is_constant_evaluated() && (std::distance(p, pend) >= 16) &&
|
||||
(step - counter >= 16) && (max_digits - digits >= 16)) {
|
||||
if (!is_constant_evaluated() && std::distance(p, pend) >= 16 &&
|
||||
step - counter >= 16 && max_digits - digits >= 16) {
|
||||
parse_sixteen_digits(p, value, counter, digits);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
while ((std::distance(p, pend) >= 8) && (step - counter >= 8) &&
|
||||
(max_digits - digits >= 8)) {
|
||||
while (std::distance(p, pend) >= 8 && step - counter >= 8 &&
|
||||
max_digits - digits >= 8) {
|
||||
parse_eight_digits(p, value, counter, digits);
|
||||
}
|
||||
while (counter < step && p != pend && digits < max_digits) {
|
||||
|
||||
@ -17,8 +17,8 @@
|
||||
#include "constexpr_feature_detect.h"
|
||||
|
||||
#define FASTFLOAT_VERSION_MAJOR 8
|
||||
#define FASTFLOAT_VERSION_MINOR 2
|
||||
#define FASTFLOAT_VERSION_PATCH 10
|
||||
#define FASTFLOAT_VERSION_MINOR 3
|
||||
#define FASTFLOAT_VERSION_PATCH 0
|
||||
|
||||
#define FASTFLOAT_STRINGIZE_IMPL(x) #x
|
||||
#define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x)
|
||||
@ -57,7 +57,11 @@ constexpr static am_pow_t am_bias_limit =
|
||||
(std::numeric_limits<int16_t>::max() / 16) - 1;
|
||||
|
||||
// Type for enum chars_format.
|
||||
#ifdef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
|
||||
typedef uint_fast8_t chars_format_t;
|
||||
#else
|
||||
typedef uint_fast16_t chars_format_t;
|
||||
#endif
|
||||
|
||||
// Type for base, only allowed from 2 to 36.
|
||||
typedef uint_fast8_t base_t;
|
||||
@ -66,8 +70,10 @@ enum class chars_format : chars_format_t;
|
||||
|
||||
#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
|
||||
namespace detail {
|
||||
constexpr chars_format basic_json_fmt = chars_format(1 << 6);
|
||||
constexpr chars_format basic_fortran_fmt = chars_format(1 << 7);
|
||||
constexpr chars_format json_fmt = chars_format(1 << 6);
|
||||
constexpr chars_format fortran_fmt = chars_format(1 << 7);
|
||||
constexpr chars_format javascript_fmt = chars_format(1 << 8);
|
||||
constexpr chars_format javascript_sloppy_fmt = chars_format(1 << 9);
|
||||
} // namespace detail
|
||||
#endif
|
||||
|
||||
@ -81,10 +87,27 @@ enum class chars_format : chars_format_t {
|
||||
allow_leading_plus = 1 << 4,
|
||||
skip_white_space = 1 << 5,
|
||||
// RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6
|
||||
json = chars_format_t(detail::basic_json_fmt) | general | no_infnan,
|
||||
json = chars_format_t(detail::json_fmt) | general | no_infnan,
|
||||
// Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed.
|
||||
json_or_infnan = chars_format_t(detail::basic_json_fmt) | general,
|
||||
fortran = chars_format_t(detail::basic_fortran_fmt) | general,
|
||||
json_or_infnan = chars_format_t(detail::json_fmt) | general,
|
||||
fortran = chars_format_t(detail::fortran_fmt) | general,
|
||||
// ECMAScript DecimalLiteral (strict mode):
|
||||
// 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 = chars_format_t(detail::javascript_fmt) | general | 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 = chars_format_t(detail::javascript_fmt) |
|
||||
chars_format_t(detail::javascript_sloppy_fmt) | general |
|
||||
no_infnan,
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -270,12 +293,12 @@ static_assert(FASTFLOAT_X86_SIMD == 20 || FASTFLOAT_X86_SIMD == 42 ||
|
||||
// clang-format off
|
||||
#ifndef FASTFLOAT_ASSERT
|
||||
#define FASTFLOAT_ASSERT(x) \
|
||||
{ ((void)(x)); }
|
||||
{ static_cast<void>(x); }
|
||||
#endif
|
||||
|
||||
#ifndef FASTFLOAT_DEBUG_ASSERT
|
||||
#define FASTFLOAT_DEBUG_ASSERT(x) \
|
||||
{ ((void)(x)); }
|
||||
{ static_cast<void>(x); }
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
|
||||
@ -357,17 +357,26 @@ from_chars_float_advanced(UC const *first, UC const *last, T &value,
|
||||
"only char, wchar_t, char16_t and char32_t are supported");
|
||||
|
||||
from_chars_result_t<UC> answer;
|
||||
#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
|
||||
if (chars_format_t(options.format & chars_format::skip_white_space)) {
|
||||
while ((first != last) && fast_float::is_space(*first)) {
|
||||
++first;
|
||||
}
|
||||
}
|
||||
#else
|
||||
#ifdef FASTFLOAT_ISNOT_CHECKED_BOUNDS
|
||||
// We are in parser code with external loop that checks bounds.
|
||||
FASTFLOAT_ASSUME(first < last);
|
||||
#endif
|
||||
#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
|
||||
if (chars_format_t(options.format & chars_format::skip_white_space)) {
|
||||
#ifndef FASTFLOAT_ISNOT_CHECKED_BOUNDS
|
||||
while (first != last && fast_float::is_space(*first)) {
|
||||
++first;
|
||||
}
|
||||
#else
|
||||
do {
|
||||
if (fast_float::is_space(*first)) {
|
||||
++first;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (first != last)
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#ifndef FASTFLOAT_ISNOT_CHECKED_BOUNDS
|
||||
if (first == last) {
|
||||
@ -377,7 +386,7 @@ from_chars_float_advanced(UC const *first, UC const *last, T &value,
|
||||
}
|
||||
#endif
|
||||
#ifndef FASTFLOAT_ONLY_POSITIVE_C_NUMBER_WO_INF_NAN
|
||||
bool const bjf = chars_format_t(options.format & detail::basic_json_fmt) != 0;
|
||||
bool const bjf = chars_format_t(options.format & detail::json_fmt) != 0;
|
||||
#endif
|
||||
// Fast path: parse WITHOUT materializing the integer/fraction spans (read
|
||||
// only by the rare slow paths). Skipping their stores keeps the fat
|
||||
|
||||
@ -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)
|
||||
|
||||
330
tests/javascript_fmt.cpp
Normal file
330
tests/javascript_fmt.cpp
Normal file
@ -0,0 +1,330 @@
|
||||
/*
|
||||
* 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/#sec-additional-syntax-numeric-literals
|
||||
*/
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
// 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