mirror of
https://github.com/nlohmann/json.git
synced 2026-10-01 04:00:31 +00:00
Prefer strtod_l over libc++'s std::from_chars
libc++'s std::from_chars for float and double is 1.3x to 2.8x slower per number than Apple's strtod_l, and it was tried before Clinger's fast path. With Apple clang in C++17 mode, parsing random doubles took 1.75x as long as on develop, short numbers such as 123.45 1.3x, and mesh.json 1.2x. Use libc++'s std::from_chars only where the C library has no strtod_l. On Apple platforms, floats are now converted by Clinger's fast path and strtod_l, which is 0.90x to 1.01x the time of develop. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -77,11 +77,11 @@ otherwise, it uses unsigned integer storage.
|
||||
[`std::strtod`](https://en.cppreference.com/w/cpp/string/byte/strtof), respectively.
|
||||
- The result of converting floating-point numbers does not depend on the C locale (`LC_NUMERIC`). They are
|
||||
converted with [`std::from_chars`](https://en.cppreference.com/w/cpp/utility/from_chars) where the standard
|
||||
library implements it for the number type (including libc++ 20 or later for `#!c float` and `#!c double`),
|
||||
otherwise with `strtod_l` and the "C" locale where the C library provides it (glibc, macOS, MSVC), and otherwise
|
||||
with `std::strtod` and the decimal point of the current locale. Before version 3.13.0, the last way was used much
|
||||
more often, and a locale whose decimal point is longer than one byte (e.g., `fa_IR.UTF-8`) truncated numbers at
|
||||
the decimal point.
|
||||
library implements it for the number type (with libc++ 20 or later, only for `#!c float` and `#!c double`, and
|
||||
only where `strtod_l` is unavailable, because that is faster), otherwise with `strtod_l` and the "C" locale where
|
||||
the C library provides it (glibc, macOS, MSVC), and otherwise with `std::strtod` and the decimal point of the
|
||||
current locale. Before version 3.13.0, the last way was used much more often, and a locale whose decimal point
|
||||
is longer than one byte (e.g., `fa_IR.UTF-8`) truncated numbers at the decimal point.
|
||||
|
||||
!!! example "Examples"
|
||||
|
||||
|
||||
@@ -20,45 +20,6 @@
|
||||
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
// std::from_chars lives in <charconv>, but being in C++17 mode does not
|
||||
// guarantee the header exists: GCC 7 sets __cplusplus to C++17 yet ships no
|
||||
// <charconv> (added in GCC 8; floating-point support in GCC 11). Guard the
|
||||
// include with __has_include so such toolchains fall back to the scalar path.
|
||||
#if defined(JSON_HAS_CPP_17) && defined(__has_include)
|
||||
#if __has_include(<charconv>)
|
||||
#include <charconv> // from_chars
|
||||
#include <system_error> // errc
|
||||
|
||||
// std::from_chars is used for floating-point numbers
|
||||
// - for float, double, and long double if __cpp_lib_to_chars announces
|
||||
// complete support (only checked in C++17 or later: some standard
|
||||
// libraries, e.g. libstdc++ 15, define it even in C++14 mode, where
|
||||
// <charconv> is not included);
|
||||
// - for float and double with libc++ 20 or later, which does not define
|
||||
// __cpp_lib_to_chars because long double is missing. On Apple
|
||||
// platforms, the implementation is part of the system's libc++ and
|
||||
// only available when deploying to macOS/iOS 26 or later; for older
|
||||
// deployment targets, _LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT
|
||||
// is 0, and the fallbacks below are used.
|
||||
#if defined(__cpp_lib_to_chars)
|
||||
#define JSON_HAS_FLOAT_FROM_CHARS 1
|
||||
#define JSON_HAS_LONG_DOUBLE_FROM_CHARS 1
|
||||
#elif defined(_LIBCPP_VERSION) && defined(_LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT)
|
||||
#if _LIBCPP_VERSION >= 200000 && _LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT
|
||||
#define JSON_HAS_FLOAT_FROM_CHARS 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef JSON_HAS_FLOAT_FROM_CHARS
|
||||
#define JSON_HAS_FLOAT_FROM_CHARS 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_HAS_LONG_DOUBLE_FROM_CHARS
|
||||
#define JSON_HAS_LONG_DOUBLE_FROM_CHARS 0
|
||||
#endif
|
||||
|
||||
// strtof_l/strtod_l/strtold_l convert with a given locale object instead of the
|
||||
// global C locale. They are not part of ISO C or C++, so they are only used where
|
||||
// the C library is known to declare them: Microsoft's UCRT (as _strtod_l etc.),
|
||||
@@ -77,6 +38,47 @@
|
||||
#define JSON_HAS_C_LOCALE_STRTOD 0
|
||||
#endif
|
||||
|
||||
// std::from_chars lives in <charconv>, but being in C++17 mode does not
|
||||
// guarantee the header exists: GCC 7 sets __cplusplus to C++17 yet ships no
|
||||
// <charconv> (added in GCC 8; floating-point support in GCC 11). Guard the
|
||||
// include with __has_include so such toolchains fall back to the scalar path.
|
||||
#if defined(JSON_HAS_CPP_17) && defined(__has_include)
|
||||
#if __has_include(<charconv>)
|
||||
#include <charconv> // from_chars
|
||||
#include <system_error> // errc
|
||||
|
||||
// std::from_chars is used for floating-point numbers
|
||||
// - for float, double, and long double if __cpp_lib_to_chars announces
|
||||
// complete support (only checked in C++17 or later: some standard
|
||||
// libraries, e.g. libstdc++ 15, define it even in C++14 mode, where
|
||||
// <charconv> is not included);
|
||||
// - for float and double with libc++ 20 or later, which does not define
|
||||
// __cpp_lib_to_chars because long double is missing, but only where
|
||||
// the C library offers no strtod_l: libc++'s implementation is slower
|
||||
// than Apple's strtod_l (by 1.3x to 2.8x per number), and it would be
|
||||
// tried before Clinger's fast path. On Apple platforms, it is also only
|
||||
// available when deploying to macOS/iOS 26 or later; for older
|
||||
// deployment targets, _LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT
|
||||
// is 0.
|
||||
#if defined(__cpp_lib_to_chars)
|
||||
#define JSON_HAS_FLOAT_FROM_CHARS 1
|
||||
#define JSON_HAS_LONG_DOUBLE_FROM_CHARS 1
|
||||
#elif !JSON_HAS_C_LOCALE_STRTOD && defined(_LIBCPP_VERSION) && defined(_LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT)
|
||||
#if _LIBCPP_VERSION >= 200000 && _LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT
|
||||
#define JSON_HAS_FLOAT_FROM_CHARS 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef JSON_HAS_FLOAT_FROM_CHARS
|
||||
#define JSON_HAS_FLOAT_FROM_CHARS 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_HAS_LONG_DOUBLE_FROM_CHARS
|
||||
#define JSON_HAS_LONG_DOUBLE_FROM_CHARS 0
|
||||
#endif
|
||||
|
||||
// This file contains the value-conversion helpers used by the lexer to turn an
|
||||
// already-validated number token into a value, where possible without the
|
||||
// locale/errno overhead of std::strtoull/std::strtod. They are free functions so
|
||||
|
||||
@@ -8505,45 +8505,6 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
// #include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
|
||||
// std::from_chars lives in <charconv>, but being in C++17 mode does not
|
||||
// guarantee the header exists: GCC 7 sets __cplusplus to C++17 yet ships no
|
||||
// <charconv> (added in GCC 8; floating-point support in GCC 11). Guard the
|
||||
// include with __has_include so such toolchains fall back to the scalar path.
|
||||
#if defined(JSON_HAS_CPP_17) && defined(__has_include)
|
||||
#if __has_include(<charconv>)
|
||||
#include <charconv> // from_chars
|
||||
#include <system_error> // errc
|
||||
|
||||
// std::from_chars is used for floating-point numbers
|
||||
// - for float, double, and long double if __cpp_lib_to_chars announces
|
||||
// complete support (only checked in C++17 or later: some standard
|
||||
// libraries, e.g. libstdc++ 15, define it even in C++14 mode, where
|
||||
// <charconv> is not included);
|
||||
// - for float and double with libc++ 20 or later, which does not define
|
||||
// __cpp_lib_to_chars because long double is missing. On Apple
|
||||
// platforms, the implementation is part of the system's libc++ and
|
||||
// only available when deploying to macOS/iOS 26 or later; for older
|
||||
// deployment targets, _LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT
|
||||
// is 0, and the fallbacks below are used.
|
||||
#if defined(__cpp_lib_to_chars)
|
||||
#define JSON_HAS_FLOAT_FROM_CHARS 1
|
||||
#define JSON_HAS_LONG_DOUBLE_FROM_CHARS 1
|
||||
#elif defined(_LIBCPP_VERSION) && defined(_LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT)
|
||||
#if _LIBCPP_VERSION >= 200000 && _LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT
|
||||
#define JSON_HAS_FLOAT_FROM_CHARS 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef JSON_HAS_FLOAT_FROM_CHARS
|
||||
#define JSON_HAS_FLOAT_FROM_CHARS 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_HAS_LONG_DOUBLE_FROM_CHARS
|
||||
#define JSON_HAS_LONG_DOUBLE_FROM_CHARS 0
|
||||
#endif
|
||||
|
||||
// strtof_l/strtod_l/strtold_l convert with a given locale object instead of the
|
||||
// global C locale. They are not part of ISO C or C++, so they are only used where
|
||||
// the C library is known to declare them: Microsoft's UCRT (as _strtod_l etc.),
|
||||
@@ -8562,6 +8523,47 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
#define JSON_HAS_C_LOCALE_STRTOD 0
|
||||
#endif
|
||||
|
||||
// std::from_chars lives in <charconv>, but being in C++17 mode does not
|
||||
// guarantee the header exists: GCC 7 sets __cplusplus to C++17 yet ships no
|
||||
// <charconv> (added in GCC 8; floating-point support in GCC 11). Guard the
|
||||
// include with __has_include so such toolchains fall back to the scalar path.
|
||||
#if defined(JSON_HAS_CPP_17) && defined(__has_include)
|
||||
#if __has_include(<charconv>)
|
||||
#include <charconv> // from_chars
|
||||
#include <system_error> // errc
|
||||
|
||||
// std::from_chars is used for floating-point numbers
|
||||
// - for float, double, and long double if __cpp_lib_to_chars announces
|
||||
// complete support (only checked in C++17 or later: some standard
|
||||
// libraries, e.g. libstdc++ 15, define it even in C++14 mode, where
|
||||
// <charconv> is not included);
|
||||
// - for float and double with libc++ 20 or later, which does not define
|
||||
// __cpp_lib_to_chars because long double is missing, but only where
|
||||
// the C library offers no strtod_l: libc++'s implementation is slower
|
||||
// than Apple's strtod_l (by 1.3x to 2.8x per number), and it would be
|
||||
// tried before Clinger's fast path. On Apple platforms, it is also only
|
||||
// available when deploying to macOS/iOS 26 or later; for older
|
||||
// deployment targets, _LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT
|
||||
// is 0.
|
||||
#if defined(__cpp_lib_to_chars)
|
||||
#define JSON_HAS_FLOAT_FROM_CHARS 1
|
||||
#define JSON_HAS_LONG_DOUBLE_FROM_CHARS 1
|
||||
#elif !JSON_HAS_C_LOCALE_STRTOD && defined(_LIBCPP_VERSION) && defined(_LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT)
|
||||
#if _LIBCPP_VERSION >= 200000 && _LIBCPP_AVAILABILITY_HAS_FROM_CHARS_FLOATING_POINT
|
||||
#define JSON_HAS_FLOAT_FROM_CHARS 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef JSON_HAS_FLOAT_FROM_CHARS
|
||||
#define JSON_HAS_FLOAT_FROM_CHARS 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_HAS_LONG_DOUBLE_FROM_CHARS
|
||||
#define JSON_HAS_LONG_DOUBLE_FROM_CHARS 0
|
||||
#endif
|
||||
|
||||
// This file contains the value-conversion helpers used by the lexer to turn an
|
||||
// already-validated number token into a value, where possible without the
|
||||
// locale/errno overhead of std::strtoull/std::strtod. They are free functions so
|
||||
|
||||
Reference in New Issue
Block a user