diff --git a/config/Jamfile b/config/Jamfile index 9f7b5fab8..1113114ba 100644 --- a/config/Jamfile +++ b/config/Jamfile @@ -6,7 +6,10 @@ import modules ; import path ; lib quadmath ; +lib double-conversion ; exe has_float128 : has_float128.cpp quadmath ; +exe has_double_conversion : has_double_conversion.cpp double-conversion ; explicit has_float128 ; +explicit has_double_conversion ; diff --git a/config/has_double_conversion.cpp b/config/has_double_conversion.cpp new file mode 100644 index 000000000..0b72ed040 --- /dev/null +++ b/config/has_double_conversion.cpp @@ -0,0 +1,19 @@ +// Copyright 2023 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +int main() +{ + using namespace double_conversion; + + const int flags = 0; + int processed; + + auto converter = StringToDoubleConverter(flags, 0.0, 0.0, "inf", "nan"); + + auto result = converter.StringToDouble("0.0", 3, &processed); + + return static_cast(result); +} diff --git a/doc/charconv.adoc b/doc/charconv.adoc index d869dbb24..e4e049e76 100644 --- a/doc/charconv.adoc +++ b/doc/charconv.adoc @@ -21,6 +21,7 @@ include::charconv/chars_format.adoc[] include::charconv/from_chars.adoc[] include::charconv/to_chars.adoc[] include::charconv/reference.adoc[] +include::charconv/benchmarks.adoc[] include::charconv/sources.adoc[] include::charconv/copyright.adoc[] diff --git a/doc/charconv/benchmarks.adoc b/doc/charconv/benchmarks.adoc new file mode 100644 index 000000000..4d2e202b0 --- /dev/null +++ b/doc/charconv/benchmarks.adoc @@ -0,0 +1,251 @@ +//// +Copyright 2023 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + += Benchmarks +:idprefix: benchmarks + +The values are relative to the performance of `std::printf` and `std::strtoX`. +Larger numbers are more performant (e.g. 2.00 means twice as fast, and 0.50 means it takes twice as long). + +== How to run the Benchmarks + +To run the benchmarks yourself navigate to the test folder, and define `BOOST_CHARCONV_RUN_BENCHMARKS` when running the tests. +An example on linux with b2: `../../../b2 cxxstd=20 toolset=gcc-13 define=BOOST_CHARCONV_RUN_BENCHMARKS STL_benchmark -a release` . + +Additionally, you will need the following: + +* A compiler with full `` support: +** GCC 11 or newer +** MSVC 19.24 or newer +* https://github.com/google/double-conversion[libdouble-conversion] + +== x86_64 Linux + +Data in tables 1 - 4 were run on Ubuntu 23.04 with x86_64 architecture using GCC 13.1.0 with libstdc++. + +=== Floating Point + +.to_chars floating point with the shortest representation +|=== +|Function|Relative Performance (float / double) + +|std::printf +|1.00 / 1.00 +|Boost.lexical_cast +|0.55 / 0.46 +|Boost.spirit.karma +|1.80 / 2.62 +|std::to_chars +|3.53 / 4.87 +|Boost.Charconv.to_chars +|3.64 / 4.92 +|Google double-conversion +|1.19 / 1.85 +|=== + +.from_chars floating point with scientific formatting +|=== +|Function|Relative Performance (float / double) + +|std::strto(f/d) +|1.00 / 1.00 +|Boost.lexical_cast +|0.33 / 0.42 +|Boost.spirit.qi +|3.17 / 4.65 +|std::from_chars +|3.23 / 5.77 +|Boost.Charconv.from_chars +|3.28 / 5.75 +|Google double-conversion +|1.16 / 1.30 +|=== + +=== Integral + +.to_chars base 10 integers +|=== +|Function|Relative Performance (uint32_t / uint64_t) + +|std::printf +|1.00 / 1.00 +|Boost.lexical_cast +|1.77 / 1.41 +|Boost.spirit.karma +|2.55 / 1.47 +|std::to_chars +|3.86 / 2.25 +|Boost.Charconv.to_chars +|3.81 / 2.25 +|=== + +.from_chars base 10 integers +|=== +|Function|Relative Performance (uint32_t / uint64_t) + +|std::strto(ul,ull) +|1.00 / 1.00 +|Boost.lexical_cast +|0.53 / 0.52 +|Boost.spirit.qi +|2.24 / 1.49 +|std::from_chars +|1.97 / 1.68 +|Boost.Charconv.from_chars +|2.54 / 1.78 +|=== + +== x86_64 Windows + +Data in tables 5 - 8 were run on Windows 11 with x86_64 architecture using MSVC 14.3 (V17.7.0). + +=== Floating Point + +.to_chars floating point with the shortest representation +|=== +|Function|Relative Performance (float / double) + +|std::printf +|1.00 / 1.00 +|Boost.lexical_cast +|0.50 / 0.70 +|Boost.spirit.karma +|2.23 / 7.58 +|std::to_chars +|5.58 / 15.77 +|Boost.Charconv.to_chars +|5.62 / 15.26 +|=== + +.from_chars floating point with scientific formatting +|=== +|Function|Relative Performance (float / double) + +|std::strto(f/d) +|1.00 / 1.00 +|Boost.lexical_cast +|0.14 / 0.20 +|Boost.spirit.qi +|2.03 / 4.58 +|std::from_chars +|1.01 / 1.23 +|Boost.Charconv.from_chars +|2.06 / 5.21 +|=== + +=== Integral + +.to_chars base 10 integers +|=== +|Function|Relative Performance (uint32_t / uint64_t) + +|std::printf +|1.00 / 1.00 +|Boost.lexical_cast +|0.68 / 0.68 +|Boost.spirit.karma +|2.75 / 1.67 +|std::to_chars +|2.75 / 2.10 +|Boost.Charconv.to_chars +|2.75 / 2.06 +|=== + +.from_chars base 10 integers +|=== +|Function|Relative Performance (uint32_t / uint64_t) + +|std::strto(ul,ull) +|1.00 / 1.00 +|Boost.lexical_cast +|0.46 / 0.39 +|Boost.spirit.qi +|1.94 / 1.63 +|std::from_chars +|2.43 / 2.18 +|Boost.Charconv.from_chars +|2.68 / 2.27 +|=== + +== ARM MacOS + +Data in tables 9-12 were run on MacOS Ventura 13.5 with M1 Pro architecture using Homebrew GCC 13.1.0 with libstdc++. + +=== Floating Point + +.to_chars floating point with the shortest representation +|=== +|Function|Relative Performance (float / double) + +|std::printf +|1.00 / 1.00 +|Boost.lexical_cast +|0.52 / 0.12 +|Boost.spirit.karma +|1.40 / 1.40 +|std::to_chars +|3.01 / 2.96 +|Boost.Charconv.to_chars +|3.03 / 2.96 +|Google double-conversion +|1.22 / 1.16 +|=== + +.from_chars floating point with scientific formatting +|=== +|Function|Relative Performance (float / double) + +|std::strto(f/d) +|1.00 / 1.00 +|Boost.lexical_cast +|0.06 / 0.06 +|Boost.spirit.qi +|1.12 / 1.06 +|std::from_chars +|1.32 / 1.65 +|Boost.Charconv.from_chars +|1.28 / 1.63 +|Google double-conversion +|0.45 / 0.32 + +|=== + +=== Integral + +.to_chars base 10 integers +|=== +|Function|Relative Performance (uint32_t / uint64_t) + +|std::printf +|1.00 / 1.00 +|Boost.lexical_cast +|2.08 / 1.75 +|Boost.spirit.karma +|4.17 / 2.06 +|std::to_chars +|6.25 / 4.12 +|Boost.Charconv.to_chars +|6.25 / 4.12 +|=== + +.from_chars base 10 integers +|=== +|Function|Relative Performance (uint32_t / uint64_t) + +|std::strto(ul,ull) +|1.00 / 1.00 +|Boost.lexical_cast +|0.56 / 0.54 +|Boost.spirit.qi +|1.39 / 1.33 +|std::from_chars +|1.92 / 1.65 +|Boost.Charconv.from_chars +|2.27 / 1.65 +|=== + +Special thanks to Stephan T. Lavavej for providing the basis for the benchmarks. + diff --git a/doc/charconv/chars_format.adoc b/doc/charconv/chars_format.adoc index f1546005e..78e3f7586 100644 --- a/doc/charconv/chars_format.adoc +++ b/doc/charconv/chars_format.adoc @@ -26,7 +26,8 @@ The integer part will be between 0 and 9 inclusive. The fraction and exponent wi The exponent will always have a minimum of 2 digits. === Fixed Format -Fixed format will be of the form `2.30` or `3090`. An exponent will not appear with this format. If the precision of `to_chars` exceeds that of the type (e.g. std::numeric_limits::chars10), 0s will be appended to the end. +Fixed format will be of the form `2.30` or `3090`. An exponent will not appear with this format. +If the precision of `to_chars` exceeds that of the type (e.g. `std::numeric_limits::chars10`), 0s will be appended to the end. === Hex Format Hex format will be of the form `1.0ep+5`. The integer part will always be 0 or 1. The exponent will be with a p instead of an e like in base 10 formats, because e is a valid hex value. @@ -45,4 +46,4 @@ Each hexadecimal digit corresponds to a specific group of bits, making it easier This can be helpful for debugging or analyzing floating-point arithmetic operations (e.g. Computing https://en.wikipedia.org/wiki/Unit_in_the_last_place[ULP] distances). === General -General format will be the shortest representation of a number in either fixed or general format (e.g. `1234` instead of `1.234e+03`. \ No newline at end of file +General format will be the shortest representation of a number in either fixed or general format (e.g. `1234` instead of `1.234e+03`. diff --git a/doc/charconv/from_chars.adoc b/doc/charconv/from_chars.adoc index dea0acb37..7427f57f0 100644 --- a/doc/charconv/from_chars.adoc +++ b/doc/charconv/from_chars.adoc @@ -35,7 +35,7 @@ from_chars_result from_chars(const char* first, const char* last, Real& value, c ** std::errc::invalid_argument - invalid argument (e.g. parsing a negative number into an unsigned type) ** std::errc::result_out_of_range - result out of range (e.g. overflow) * operator== - compares the values of ptr and ec for equality -* operator!- - compares the value of ptr and ec for inequality +* operator! - compares the value of ptr and ec for inequality == from_chars * first, last - valid range to parse @@ -53,7 +53,7 @@ from_chars_result from_chars(const char* first, const char* last, Real& value, c * On std::errc::result_out_of_range we return ±0 for small values (e.g. 1.0e-99999) or ±HUGE_VAL for large values (e.g. 1.0e+99999) to match the handling of `std::strtod`. This is a divergence from the standard which states we should return the `value` argument unmodified. * These functions have been tested to support all built-in floating-point types and those from C++23's `` -** Long doubles can be either 64, 80, or 128-bit but must be IEEE 754 compliant. An example of a non-compliant, and therefore unsupported format is `ibm128`. +** Long doubles can be either 64, 80, or 128-bit, but must be IEEE 754 compliant. An example of a non-compliant, and therefore unsupported format is `ibm128`. ** Use of `__float128` or `std::float128_t` requires compiling with `-std=gnu++xx` and linking GCC's `libquadmath`. == Examples diff --git a/doc/charconv/overview.adoc b/doc/charconv/overview.adoc index a5d2cdbba..0c9d0b44f 100644 --- a/doc/charconv/overview.adoc +++ b/doc/charconv/overview.adoc @@ -35,7 +35,7 @@ assert(!strncmp(buffer, "123456", 6)); // Strncmp returns 0 on match == Supported Compilers -* GCC 4.8 or later +* GCC 5 or later * Clang 3.7 or later * Visual Studio 2015 (14.0) or later diff --git a/doc/charconv/to_chars.adoc b/doc/charconv/to_chars.adoc index fa0cc255c..5a05d315f 100644 --- a/doc/charconv/to_chars.adoc +++ b/doc/charconv/to_chars.adoc @@ -20,13 +20,13 @@ struct to_chars_result }; template -BOOST_CHARCONV_CONSTEXPR to_chars_result(char* first, char* last, Integral value, int base = 10) noexcept; +BOOST_CHARCONV_CONSTEXPR to_chars_result to_chars(char* first, char* last, Integral value, int base = 10) noexcept; template -BOOST_CHARCONV_CONSTEXPR to_chars_result(char* first, char* last, Integral value, int base) noexcept = delete; +BOOST_CHARCONV_CONSTEXPR to_chars_result to_chars(char* first, char* last, Integral value, int base) noexcept = delete; template -to_chars_result(char* first, char* last, Real value, chars_format fmt = chars_format::general, int precision) noexcept; +to_chars_result to_chars(char* first, char* last, Real value, chars_format fmt = chars_format::general, int precision) noexcept; ---- == to_chars_result @@ -48,7 +48,7 @@ See xref:chars_format.adoc[chars_format overview] for description. === to_chars for integral types * All built-in integral types are allowed except bool which is deleted -* from_chars for integral type is constexpr (BOOST_CHARCONV_CONSTEXPR is defined) when compiled using `-std=c++14` or newer and a compiler with `__builtin_ is_constant_evaluated` +* from_chars for integral type is constexpr (BOOST_CHARCONV_CONSTEXPR is defined) when compiled using `-std=c++14` or newer and a compiler with `\__builtin_ is_constant_evaluated` * These functions have been tested to support `\__int128` and `unsigned __int128` === to_chars for floating point types diff --git a/reporting/performance/charconv_benchmark.cpp b/reporting/performance/charconv_benchmark.cpp deleted file mode 100644 index c588dc572..000000000 --- a/reporting/performance/charconv_benchmark.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// (C) Copyright Matt Borland 2023. -// Use, modification and distribution are subject to the -// Boost Software License, Version 1.0. (See accompanying file -// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - -#include -#include -#include - -template -void std_integer_from_chars(benchmark::State& state) -{ - const char* buffer = "12345"; - T v {}; - - for (auto _ : state) - { - benchmark::DoNotOptimize(std::from_chars(buffer, buffer + std::strlen(buffer), v)); - } -} - -BENCHMARK_TEMPLATE(std_integer_from_chars, int); -BENCHMARK_TEMPLATE(std_integer_from_chars, unsigned); -BENCHMARK_TEMPLATE(std_integer_from_chars, long); -BENCHMARK_TEMPLATE(std_integer_from_chars, unsigned long); -BENCHMARK_TEMPLATE(std_integer_from_chars, long long); -BENCHMARK_TEMPLATE(std_integer_from_chars, unsigned long long); - -BENCHMARK_MAIN(); - -/* -Apple clang version 14.0.0 (clang-1400.0.29.202) -Target: arm64-apple-darwin22.2.0 -Thread model: posix -InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin - -Running ./charcov_benchmark -Run on (10 X 24.0704 MHz CPU s) -CPU Caches: - L1 Data 64 KiB - L1 Instruction 128 KiB - L2 Unified 4096 KiB (x10) -Load Average: 2.23, 1.94, 2.12 -------------------------------------------------------------------------------------- -Benchmark Time CPU Iterations -------------------------------------------------------------------------------------- -std_integer_from_chars 37.3 ns 37.3 ns 18730689 -std_integer_from_chars 30.8 ns 30.8 ns 22774226 -std_integer_from_chars 37.5 ns 37.5 ns 18690341 -std_integer_from_chars 31.2 ns 31.2 ns 22543049 -std_integer_from_chars 37.5 ns 37.5 ns 18708324 -std_integer_from_chars 31.2 ns 31.2 ns 22555688 -*/ diff --git a/test/Jamfile b/test/Jamfile index cda528fae..d63e2f91f 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -17,6 +17,7 @@ project : requirements gcc:on ; lib quadmath ; +lib double-conversion ; run quick.cpp ; @@ -40,4 +41,5 @@ run to_chars_float.cpp ; run test_boost_json_values.cpp ; run to_chars_float_STL_comp.cpp : : : [ requires cxx17_hdr_charconv ] ; run from_chars_float2.cpp ; +run-fail STL_benchmark.cpp : : : [ requires cxx17_hdr_charconv ] [ check-target-builds ../config//has_double_conversion "Google double-coversion support" : "double-conversion" ] ; run test_float128.cpp : : : [ check-target-builds ../config//has_float128 "GCC libquadmath and __float128 support" : "quadmath" ] ; diff --git a/test/STL_benchmark.cpp b/test/STL_benchmark.cpp new file mode 100644 index 000000000..9c6914722 --- /dev/null +++ b/test/STL_benchmark.cpp @@ -0,0 +1,1047 @@ +// (C) Copyright Stephan T. Lavavej 2019 - 2023. +// (C) Copyright Matt Borland 2023. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Derived from mailing list post: https://lists.boost.org/Archives/boost/2023/05/254660.php + +#include + +#ifdef BOOST_CHARCONV_RUN_BENCHMARKS +#ifdef __GNUC__ +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + +#ifndef _MSC_VER +#define AVOID_SPRINTF_S +#endif // _MSC_VER + +#ifndef AVOID_CHARCONV +#include +#endif // AVOID_CHARCONV +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; + +void verify(const bool b) { + if (!b) { + puts("VERIFICATION FAILURE"); + exit(EXIT_FAILURE); + } +} + +template +void verify_fp(T x, T y, int tol = 10) { + if (abs(boost::math::float_distance(x, y)) > tol) + { + puts("VERIFICATION FAILURE"); + exit(EXIT_FAILURE); + // cerr << "Float distance between: " << x << " and " << y << " is " << abs(boost::math::float_distance(x, y)) << endl; + } +} + +enum class RoundTrip { Sci, Fix, Gen, Hex, Lossy, u32, u64 }; + +constexpr size_t N = 200; // how many values to test + +constexpr size_t K = 5; // how many times to repeat the test, for cleaner timing + +constexpr size_t BufSize = 2'000'000; // more than enough + +unsigned int global_dummy = 0; + +template +struct scientific_policy : boost::spirit::karma::real_policies +{ + static bool trailing_zeros(Num) + { + return false; + } + + static unsigned precision(Num) + { + return std::numeric_limits::digits10 + 1; + } +}; + +template +void test_boost_spirit_karma(const char* const str, const vector& vec) { + namespace karma = boost::spirit::karma; + + using science_type_double = karma::real_generator>; + science_type_double const scientific_double = science_type_double(); + using science_type_float = karma::real_generator>; + science_type_float const scientific_float = science_type_float(); + + const auto start = steady_clock::now(); + + for (size_t k = 0; k < K; ++k) { + for (const auto& elem : vec) { + char buffer[BufSize]; + char* it = buffer; + + if constexpr (std::is_same_v) { + karma::generate(it, scientific_float, elem); + } else if constexpr (std::is_same_v) { + karma::generate(it, scientific_double, elem); + } else if constexpr (std::is_same_v) { + karma::generate(it, karma::ulong_, (unsigned long)elem); + } else if constexpr (std::is_same_v) { + karma::generate(it, karma::ulong_long, (unsigned long long)elem); + } + } + } + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + for (const auto& elem : vec) { + char buffer[BufSize] {}; + char* it = buffer; + + if constexpr (std::is_same_v) { + karma::generate(it, scientific_float, elem); + } else if constexpr (std::is_same_v) { + karma::generate(it, scientific_double, elem); + } else if constexpr (std::is_same_v) { + karma::generate(it, karma::ulong_, (unsigned long)elem); + } else if constexpr (std::is_same_v) { + karma::generate(it, karma::ulong_long, (unsigned long long)elem); + } + + T round_trip; + auto r = from_chars(buffer, buffer + BufSize, round_trip); + verify(r.ec == std::errc()); + + if constexpr (std::is_integral_v) { + verify(round_trip == elem); + } else { + // Have to set a loose tolerance, + // because even with the user specified policy rounding errors can be large + // + // Float distance between: -0.0036966 and -0.00369657 is 149 + // Float distance between: 0.002663 and 0.00266298 is 88 + // Float distance between: -0.0025576 and -0.00255758 is 107 + verify_fp(round_trip, elem, 500); + } + + } +} + +template +void test_lexical_cast(const char* const str, const vector& vec) { + const auto start = steady_clock::now(); + for (size_t k = 0; k < K; ++k) { + for (const auto& elem : vec) { + const auto ret = boost::lexical_cast>(elem); // "-1.2345678901234567e-100" plus null term + global_dummy += static_cast(ret[0]); + } + } + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + for (const auto& elem : vec) { + const auto ret = boost::lexical_cast>(elem); + + T round_trip; + auto r = from_chars(ret.data(), ret.data() + ret.size(), round_trip); + verify(r.ec == std::errc()); + + if constexpr (std::is_integral_v) { + verify(round_trip == elem); + } else { + verify_fp(round_trip, elem, 2); + } + + } +} + +template +int sprintf_wrapper(char (&buf)[BufSize], const char* const fmt, const Floating elem) { +#ifdef AVOID_SPRINTF_S + return snprintf(buf, sizeof(buf), fmt, elem); +#else // AVOID_SPRINTF_S + return sprintf_s(buf, BufSize, fmt, elem); +#endif // AVOID_SPRINTF_S +} + +template +void test_sprintf(const char* const str, const vector& vec, const char* const fmt) { + + char buf[BufSize]; + + const auto start = steady_clock::now(); + for (size_t k = 0; k < K; ++k) { + for (const auto& elem : vec) { + int ret; + if constexpr (std::is_same_v) + ret = sprintf_wrapper(buf, fmt, (unsigned long)elem); + else if constexpr (std::is_same_v) + ret = sprintf_wrapper(buf, fmt, (unsigned long long)elem); + else + ret = sprintf_wrapper(buf, fmt, elem); + + global_dummy += static_cast(ret); + global_dummy += static_cast(buf[0]); + } + } + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + for (const auto& elem : vec) { + if constexpr (std::is_same_v) + verify(sprintf_wrapper(buf, fmt, (unsigned long)elem) != -1); + else if constexpr (std::is_same_v) + verify(sprintf_wrapper(buf, fmt, (unsigned long long)elem) != -1); + else + verify(sprintf_wrapper(buf, fmt, elem) != -1); + + if constexpr (RT == RoundTrip::Lossy) { + // skip lossy conversions + } else if constexpr (is_same_v) { + verify(strtof(buf, nullptr) == elem); + } else if constexpr (is_same_v) { + verify(strtod(buf, nullptr) == elem); + } else if constexpr (is_same_v) { + verify(strtoul(buf, nullptr, 10) == (unsigned long)elem); + } else if constexpr (is_same_v) { + verify(strtoull(buf, nullptr, 10) == (unsigned long long)elem); + } + } +} + +#ifndef AVOID_CHARCONV +constexpr chars_format chars_format_from_RoundTrip(const RoundTrip rt) { + switch (rt) { + case RoundTrip::Sci: + return chars_format::scientific; + case RoundTrip::Fix: + return chars_format::fixed; + case RoundTrip::Gen: + return chars_format::general; + case RoundTrip::Hex: + return chars_format::hex; + case RoundTrip::Lossy: + default: + puts("CHARS FORMAT FAIL"); + exit(EXIT_FAILURE); + } +} + +constexpr boost::charconv::chars_format boost_chars_format_from_RoundTrip(const RoundTrip rt) { + switch (rt) { + case RoundTrip::Sci: + return boost::charconv::chars_format::scientific; + case RoundTrip::Fix: + return boost::charconv::chars_format::fixed; + case RoundTrip::Gen: + return boost::charconv::chars_format::general; + case RoundTrip::Hex: + return boost::charconv::chars_format::hex; + case RoundTrip::Lossy: + default: + puts("CHARS FORMAT FAIL"); + exit(EXIT_FAILURE); + } +} + +template +void test_STL_to_chars(const char* const str, const vector& vec, const Args&... args) { + + char buf[BufSize]; + + const auto start = steady_clock::now(); + for (size_t k = 0; k < K; ++k) { + for (const auto& elem : vec) { + const auto result = to_chars(buf, buf + BufSize, elem, args...); + + global_dummy += static_cast(result.ptr - buf); + global_dummy += static_cast(buf[0]); + } + } + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + for (const auto& elem : vec) { + const auto result = to_chars(buf, buf + BufSize, elem, args...); + verify(result.ec == errc{}); + + if constexpr (RT == RoundTrip::Lossy) { + // skip lossy conversions + } else { + T round_trip; + from_chars_result from_result; + if constexpr (std::is_same_v || std::is_same_v) + from_result = std::from_chars(buf, result.ptr, round_trip, args...); + else + from_result = std::from_chars(buf, result.ptr, round_trip, chars_format_from_RoundTrip(RT)); + verify(from_result.ec == errc{}); + verify(from_result.ptr == result.ptr); + verify(round_trip == elem); + } + } +} + +template +void test_boost_to_chars(const char* const str, const vector& vec, const Args&... args) { + + char buf[BufSize] {}; + + const auto start = steady_clock::now(); + for (size_t k = 0; k < K; ++k) { + for (const auto& elem : vec) { + const auto result = to_chars(buf, buf + BufSize, elem, args...); + + global_dummy += static_cast(result.ptr - buf); + global_dummy += static_cast(buf[0]); + } + } + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + for (const auto& elem : vec) { + const auto result = to_chars(buf, buf + BufSize, elem, args...); + if (result.ec != errc()) + { + std::cerr << "To chars failure with: " << elem + << "\n to_chars value: " << buf << std::endl; + } + + if constexpr (RT == RoundTrip::Lossy) { + // skip lossy conversions + } else { + T round_trip; + boost::charconv::from_chars_result from_result; + if constexpr (std::is_same_v || std::is_same_v) + from_result = boost::charconv::from_chars(buf, result.ptr, round_trip, args...); + else + from_result = boost::charconv::from_chars(buf, result.ptr, round_trip, boost_chars_format_from_RoundTrip(RT)); + + if (from_result.ec != errc() || from_result.ptr != result.ptr || round_trip != elem) + { + std::cerr << std::setprecision(std::numeric_limits::digits10 + 1) + << "Roundtrip failure with: " << elem + << "\n to_chars val: " << buf + << "\n from_chars val: " << round_trip + << "\n from_chars ptr: " << static_cast(from_result.ptr - buf) + << "\n to_chars ptr: " << static_cast(result.ptr - buf) << std::endl; + } + } + } +} + +template +void test_google_double_conversion_to_string(const char* const str, const vector& values) +{ + using namespace double_conversion; + + const int kBufferSize = 2000; + char buffer[kBufferSize]; + StringBuilder builder(buffer, kBufferSize); + const int flags = DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN; + DoubleToStringConverter dc(flags, "inf", "nan", 'e', -6, 21, 0, 0, 2); + + vector converted_values(N); + + const auto start = steady_clock::now(); + for (size_t k = 0; k < K; ++k) { + for (size_t n = 0; n < N; ++n) { + if constexpr (std::is_same_v) { + dc.ToShortest(values[n], &builder); + } else { + dc.ToShortestSingle(values[n], &builder); + } + + converted_values[n] = builder.Finalize(); + builder.Reset(); + } + } + + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + for (size_t n = 0; n < N; ++n) { + T round_trip {}; + auto from_result = from_chars(converted_values[n].data(), converted_values[n].data() + converted_values[n].size(), round_trip); + verify_fp(round_trip, values[n], 1); + verify(from_result.ec == errc()); + } +} + +#endif // AVOID_CHARCONV + +template +vector prepare_strings(const vector& vec) { + vector output; + + char buf[BufSize]; + + for (const auto& elem : vec) { + int ret; + + if constexpr (RT == RoundTrip::Sci) { + if constexpr (is_same_v) { + ret = sprintf_wrapper(buf, "%.8e", elem); + } else { + ret = sprintf_wrapper(buf, "%.16e", elem); + } + } else if constexpr (RT == RoundTrip::Hex) { + if constexpr (is_same_v) { + ret = sprintf_wrapper(buf, "%.6a", elem); + } else { + ret = sprintf_wrapper(buf, "%.13a", elem); + } + } else if constexpr (RT == RoundTrip::u32) { + ret = sprintf_wrapper(buf, "%lu", elem); + } else { + static_assert(RT == RoundTrip::u64); + ret = sprintf_wrapper(buf, "%llu", elem); + } + + verify(ret != -1); + + output.insert(output.end(), buf, buf + ret + 1); // include null terminator + } + + return output; +} + +template +void test_strtox(const char* const str, const vector& original, const vector& strings) { + + vector round_trip(N); + + const auto start = steady_clock::now(); + for (size_t k = 0; k < K; ++k) { + const char* ptr = strings.data(); + char* endptr = nullptr; + for (size_t n = 0; n < N; ++n) { + if constexpr (is_same_v) { + round_trip[n] = strtof(ptr, &endptr); + } else { + round_trip[n] = strtod(ptr, &endptr); + } + + ptr = endptr + 1; // advance past null terminator + } + } + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + verify(round_trip == original); +} + +template +void test_google_double_conversion_from_chars(const char* const str, const vector& original, const vector& strings) { + + using namespace double_conversion; + + const char* const last = strings.data() + strings.size(); + + vector round_trip(N); + + const int flags = StringToDoubleConverter::ALLOW_CASE_INSENSITIVITY; + int processed; + + auto converter = StringToDoubleConverter(flags, 0.0, 0.0, "inf", "nan"); + + const auto start = steady_clock::now(); + for (size_t k = 0; k < K; ++k) { + const char* ptr = strings.data(); + size_t i = 0; + while (ptr != last) + { + const auto len = strlen(ptr); + round_trip[i] = converter.StringToDouble(ptr, len, &processed); + ptr += processed + 1; + ++i; + } + } + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + for (size_t n = 0; n < N; ++n) + { + verify_fp(original[n], round_trip[n], 1); + } +} + +#ifndef AVOID_CHARCONV +vector erase_0x(const vector& strings) { + vector output; + output.reserve(strings.size() - 2 * N); + + for (auto i = strings.begin(); i != strings.end();) { + if (*i == '-') { + output.push_back('-'); + i += 3; // advance past "-0x"; + } else { + i += 2; // advance past "0x"; + } + + for (;;) { + const char c = *i++; + output.push_back(c); + if (c == '\0') { + break; + } + } + } + + return output; +} + +template +void test_from_chars(const char* const str, const vector& original, const vector& strings) { + + const char* const last = strings.data() + strings.size(); + + vector round_trip(N); + + const auto start = steady_clock::now(); + for (size_t k = 0; k < K; ++k) { + const char* first = strings.data(); + for (size_t n = 0; n < N; ++n) { + const auto from_result = from_chars(first, last, round_trip[n], chars_format_from_RoundTrip(RT)); + first = from_result.ptr + 1; // advance past null terminator + } + } + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + verify(round_trip == original); +} + +template +void test_boost_from_chars(const char* const str, const vector& original, const vector& strings) { + + const char* const last = strings.data() + strings.size(); + + vector round_trip(N); + + const auto start = steady_clock::now(); + for (size_t k = 0; k < K; ++k) { + const char* first = strings.data(); + for (size_t n = 0; n < N; ++n) { + const auto from_result = boost::charconv::from_chars(first, last, round_trip[n], boost_chars_format_from_RoundTrip(RT)); + first = from_result.ptr + 1; // advance past null terminator + } + } + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + verify(round_trip == original); +} + +template +void test_strtox_integer(const char* const str, const vector& original, const vector& strings) { + + vector round_trip(N); + + const auto start = steady_clock::now(); + + for (size_t k = 0; k < K; ++k) { + const char* first = strings.data(); + char* end = nullptr; + for (size_t n = 0; n < N; ++n) { + if constexpr (std::is_same_v) { + round_trip[n] = std::strtoul(first, &end, base); + } + else if constexpr (std::is_same_v) { + round_trip[n] = std::strtoull(first, &end, base); + } + + first = end + 1; // Advance past the null terminator + } + } + + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + verify(round_trip == original); +} + +template +void test_from_chars_integer(const char* const str, [[maybe_unused]] const vector& original, const vector& strings) { + + const char* const last = strings.data() + strings.size(); + + vector round_trip(N); + + const auto start = steady_clock::now(); + + for (size_t k = 0; k < K; ++k) { + const char* first = strings.data(); + for (size_t n = 0; n < N; ++n) { + const auto from_result = from_chars(first, last, round_trip[n], base); + first = from_result.ptr + 1; // Advance past the null terminator + } + } + + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + verify(round_trip == original); +} + +template +void test_boost_from_chars_integer(const char* const str, [[maybe_unused]] const vector& original, const vector& strings) { + + const char* const last = strings.data() + strings.size(); + + vector round_trip(N); + + const auto start = steady_clock::now(); + for (size_t k = 0; k < K; ++k) { + const char* first = strings.data(); + for (size_t n = 0; n < N; ++n) { + const auto from_result = boost::charconv::from_chars(first, last, round_trip[n], base); + first = from_result.ptr + 1; // advance past null terminator + } + } + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + verify(round_trip == original); +} + +template +bool parse_numbers(Iterator first, Iterator last, std::vector& v) +{ + namespace qi = boost::spirit::qi; + namespace ascii = boost::spirit::ascii; + namespace phoenix = boost::phoenix; + using qi::double_; + using qi::float_; + using qi::phrase_parse; + using qi::_1; + using ascii::space; + using phoenix::push_back; + using qi::ulong_; + using qi::ulong_long; + using boost::spirit::qi::uint_parser; + using boost::spirit::qi::real_parser; + using boost::spirit::qi::real_policies; + + bool r = false; + + if constexpr (std::is_same_v || std::is_same_v) + { + real_parser> float_parser; + + if (qi::parse(first, last, float_parser)) + { + T n; + auto iter = first; + size_t i = 0; + + while (qi::parse(iter, last, '\0') && qi::parse(iter, last, float_parser, n)) + { + v[i] = n; + ++i; + first = iter + 1; // Skip null terminator + } + + return true; + } + return false; + } + else + { + uint_parser max_uint; + + if (qi::parse(first, last, max_uint)) + { + uint64_t n; + auto iter = first; + size_t i = 0; + + while (qi::parse(iter, last, '\0') && qi::parse(iter, last, max_uint, n)) + { + v[i] = n; + ++i; + first = iter + 1; // Skip null terminator + } + + return true; + } + return false; + } + + return r; +} + +template +void test_boost_spirit_qi(const char* const str, BOOST_ATTRIBUTE_UNUSED const vector& original, const vector& strings) { + + const char* const last = strings.data() + strings.size(); + + vector round_trip(N); + + const auto start = steady_clock::now(); + for (size_t k = 0; k < K; ++k) { + const char* first = strings.data(); + parse_numbers(first, last, round_trip); + } + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + if constexpr (std::is_same_v || std::is_same_v) { + for (size_t n = 1; n < N; ++n) { + verify(original[n] == round_trip[n - 1]); + } + } else { + for (size_t n = 1; n < N; ++n) { + verify_fp(original[n], round_trip[n - 1], 2); + } + } +} + +template +void test_boost_lexical_cast_parse(const char* const str, const vector& original, const vector& strings) { + + //const char* const last = strings.data() + strings.size(); + + vector round_trip(N); + + const auto start = steady_clock::now(); + for (size_t k = 0; k < K; ++k) + { + const char* first = strings.data(); + for (size_t n = 0; n < N; ++n) + { + const auto len = strlen(first); + if constexpr (std::is_same_v || std::is_same_v) + round_trip[n] = boost::lexical_cast(first, len); + else if constexpr (std::is_same_v) + round_trip[n] = boost::lexical_cast(first, len); + else if constexpr (std::is_same_v) + round_trip[n] = boost::lexical_cast(first, len); + first += len + 1; + } + } + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + if constexpr (std::is_same_v) { + verify(round_trip == original); + } else if constexpr (std::is_floating_point_v) { + for (size_t n = 0; n < N; ++n) { + verify_fp(round_trip[n], original[n], 2); + } + } +} + +/* +template +void test_boost_from_chars_parser(const char* const str, const vector&, const vector& strings) { + + const char* const last = strings.data() + strings.size(); + + vector round_trip(N); + + bool sign {}; + std::uint64_t significand {}; + std::int64_t exponent {}; + + const auto start = steady_clock::now(); + for (size_t k = 0; k < K; ++k) { + const char* first = strings.data(); + for (size_t n = 0; n < N; ++n) { + const auto from_result = boost::charconv::detail::parser(first, last, sign, significand, exponent, boost_chars_format_from_RoundTrip(RT)); + first = from_result.ptr + 1; // advance past null terminator + } + } + const auto finish = steady_clock::now(); + + printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); + + // verify(round_trip == original); +} +*/ + +#endif // AVOID_CHARCONV + +void test_all() { +#if defined(__clang__) && defined(_M_IX86) + const char* const toolset = "Clang/LLVM x86 + MSVC STL"; +#elif defined(__clang__) && defined(_M_X64) + const char* const toolset = "Clang/LLVM x64 + MSVC STL"; +#elif !defined(__clang__) && defined(_M_IX86) + const char* const toolset = "C1XX/C2 x86 + MSVC STL"; +#elif !defined(__clang__) && defined(_M_X64) + const char* const toolset = "C1XX/C2 x64 + MSVC STL"; +#elif defined(__clang__) && defined(__APPLE__) && defined(__arm64__) + const char* const toolset = "Clang/LLVM Apple ARM + libstdc++"; +#elif !defined(__clang__) && defined(__APPLE__) && defined(__arm64__) + const char* const toolset = "GCC " BOOST_STRINGIZE(__GNUC__) " Apple ARM + libstdc++"; +#elif defined(__clang__) && defined(__x86_64__) + const char* const toolset = "Clang/LLVM " BOOST_STRINGIZE(__clang_major__) " x64 + libstdc++" +#elif defined(__GNUC__) && defined(__x86_64__) + const char* const toolset = "GCC " BOOST_STRINGIZE(__GNUC__) " x64 + libstdc++" ; +#else + const char* const toolset = "Unknown Toolset"; +#endif + puts(toolset); + + + vector vec_flt; + vector vec_dbl; + vector vec_u32; + vector vec_u64; + + { + mt19937_64 mt64; + + vec_flt.reserve(N); + while (vec_flt.size() < N) { + const uint32_t val = static_cast(mt64()); + constexpr uint32_t inf_nan = 0x7F800000U; + if ((val & inf_nan) == inf_nan) { + continue; // skip INF/NAN + } + float flt; + static_assert(sizeof(flt) == sizeof(val)); + memcpy(&flt, &val, sizeof(flt)); + vec_flt.push_back(flt); + } + + vec_dbl.reserve(N); + while (vec_dbl.size() < N) { + const uint64_t val = mt64(); + constexpr uint64_t inf_nan = 0x7FF0000000000000ULL; + if ((val & inf_nan) == inf_nan) { + continue; // skip INF/NAN + } + double dbl; + static_assert(sizeof(dbl) == sizeof(val)); + memcpy(&dbl, &val, sizeof(dbl)); + vec_dbl.push_back(dbl); + } + + vec_u32.reserve(N); + while (vec_u32.size() < N) { + vec_u32.emplace_back(static_cast(mt64())); + } + + vec_u64.reserve(N); + while (vec_u64.size() < N) { + vec_u64.emplace_back(mt64()); + } + } + + puts("\n-----to_chars float-----"); + + test_sprintf("std::sprintf float plain shortest", vec_flt, "%.g"); + test_sprintf("std::sprintf double plain shortest", vec_dbl, "%.g"); + + test_lexical_cast("Boost.lexical_cast float", vec_flt); + test_lexical_cast("Boost.lexical_cast double", vec_dbl); + + test_boost_spirit_karma("Boost.spirit.karma float", vec_flt); + test_boost_spirit_karma("Boost.spirit.karma double", vec_dbl); + + /* + test_sprintf("std::sprintf float scientific 8", vec_flt, "%.8e"); + test_sprintf("std::sprintf double scientific 16", vec_dbl, "%.16e"); + + test_sprintf("std::sprintf float fixed 6 (lossy)", vec_flt, "%f"); + test_sprintf("std::sprintf double fixed 6 (lossy)", vec_dbl, "%f"); + + test_sprintf("std::sprintf float general 9", vec_flt, "%.9g"); + test_sprintf("std::sprintf double general 17", vec_dbl, "%.17g"); + + test_sprintf("std::sprintf float hex 6", vec_flt, "%.6a"); + test_sprintf("std::sprintf double hex 13", vec_dbl, "%.13a"); + */ + #ifndef AVOID_CHARCONV + test_STL_to_chars("std::to_chars float plain shortest", vec_flt); + test_STL_to_chars("std::to_chars double plain shortest", vec_dbl); + test_boost_to_chars("Boost.Charconv::to_chars float plain shortest", vec_flt); + test_boost_to_chars("Boost.Charconv::to_chars double plain shortest", vec_dbl); + + test_google_double_conversion_to_string("double-conversion float plain shortest", vec_flt); + test_google_double_conversion_to_string("double-conversion double plain shortest", vec_dbl); +/* + test_STL_to_chars("std::to_chars float scientific shortest", vec_flt, chars_format::scientific); + test_STL_to_chars("std::to_chars double scientific shortest", vec_dbl, chars_format::scientific); + test_boost_to_chars("Boost.Charconv::to_chars float scientific shortest", vec_flt, boost::charconv::chars_format::scientific); + test_boost_to_chars("Boost.Charconv::to_chars double scientific shortest", vec_dbl, boost::charconv::chars_format::scientific); + + test_STL_to_chars("std::to_chars float fixed shortest", vec_flt, chars_format::fixed); + test_STL_to_chars("std::to_chars double fixed shortest", vec_dbl, chars_format::fixed); + test_boost_to_chars("Boost.Charconv::to_chars float fixed shortest", vec_flt, boost::charconv::chars_format::fixed); + test_boost_to_chars("Boost.Charconv::to_chars double fixed shortest", vec_dbl, boost::charconv::chars_format::fixed); + + test_STL_to_chars("std::to_chars float general shortest", vec_flt, chars_format::general); + test_STL_to_chars("std::to_chars double general shortest", vec_dbl, chars_format::general); + test_boost_to_chars("Boost.Charconv::to_chars float general shortest", vec_flt, boost::charconv::chars_format::general); + test_boost_to_chars("Boost.Charconv::to_chars double general shortest", vec_dbl, boost::charconv::chars_format::general); + + test_STL_to_chars("std::to_chars float hex shortest", vec_flt, chars_format::hex); + test_STL_to_chars("std::to_chars double hex shortest", vec_dbl, chars_format::hex); + test_boost_to_chars("Boost.Charconv::to_chars float hex shortest", vec_flt, boost::charconv::chars_format::hex); + test_boost_to_chars("Boost.Charconv::to_chars double hex shortest", vec_dbl, boost::charconv::chars_format::hex); + + test_STL_to_chars("std::to_chars float scientific 8", vec_flt, chars_format::scientific, 8); + test_STL_to_chars("std::to_chars double scientific 16", vec_dbl, chars_format::scientific, 16); + test_boost_to_chars("Boost.Charconv::to_chars float scientific 8", vec_flt, boost::charconv::chars_format::scientific, 8); + test_boost_to_chars("Boost.Charconv::to_chars double scientific 16", vec_dbl, boost::charconv::chars_format::scientific, 16); + + test_STL_to_chars("std::to_chars float fixed 6 (lossy)", vec_flt, chars_format::fixed, 6); + test_STL_to_chars("std::to_chars double fixed 6 (lossy)", vec_dbl, chars_format::fixed, 6); + test_boost_to_chars("Boost.Charconv::to_chars float fixed 6 (lossy)", vec_flt, boost::charconv::chars_format::fixed, 6); + test_boost_to_chars("Boost.Charconv::to_chars double fixed 6 (lossy)", vec_dbl, boost::charconv::chars_format::fixed, 6); + + test_STL_to_chars("std::to_chars float general 9", vec_flt, chars_format::general, 9); + test_STL_to_chars("std::to_chars double general 17", vec_dbl, chars_format::general, 17); + test_boost_to_chars("Boost.Charconv::to_chars float general 9", vec_flt, chars_format::general, 9); + test_boost_to_chars("Boost.Charconv::to_chars double general 17", vec_dbl, chars_format::general, 17); + + test_STL_to_chars("std::to_chars float hex 6", vec_flt, chars_format::hex, 6); + test_STL_to_chars("std::to_chars double hex 13", vec_dbl, chars_format::hex, 13); + test_boost_to_chars("Boost.Charconv::to_chars float hex 6", vec_flt, chars_format::hex, 6); + test_boost_to_chars("Boost.Charconv::to_chars double hex 13", vec_dbl, chars_format::hex, 13); +*/ + #endif // AVOID_CHARCONV + + puts("\n------to_chars int------"); + + test_sprintf("std::sprintf uint32_t", vec_u32, "%lu"); + test_sprintf("std::sprintf uint64_t", vec_u64, "%llu"); + + test_lexical_cast("Boost.lexical_cast uint32_t", vec_u32); + test_lexical_cast("Boost.lexical_cast uint64_t", vec_u64); + + test_boost_spirit_karma("Boost.spirit.karma uint32_t", vec_u32); + test_boost_spirit_karma("Boost.spirit.karma uint64_t", vec_u64); + + test_STL_to_chars("std::to_chars uint32_t", vec_u32, 10); + test_STL_to_chars("std::to_chars uint64_t", vec_u64, 10); + + test_boost_to_chars("Boost.Charconv::to_chars uint32_t", vec_u32, 10); + test_boost_to_chars("Boost.Charconv::to_chars uint64_t", vec_u64, 10); + + puts("\n----from_chars float----"); + + const vector strings_sci_flt = prepare_strings(vec_flt); + const vector strings_sci_dbl = prepare_strings(vec_dbl); + + const vector strings_hex_flt = prepare_strings(vec_flt); + const vector strings_hex_dbl = prepare_strings(vec_dbl); + + const vector strings_u32 = prepare_strings(vec_u32); + const vector strings_u64 = prepare_strings(vec_u64); + + test_strtox("std::strtof float scientific", vec_flt, strings_sci_flt); + test_strtox("std::strtod double scientific", vec_dbl, strings_sci_dbl); + + test_boost_lexical_cast_parse("Boost.lexical_cast float scientific", vec_flt, strings_sci_flt); + test_boost_lexical_cast_parse("Boost.lexical_cast double scientific", vec_dbl, strings_sci_dbl); + + test_boost_spirit_qi("Boost.Spirit.Qi float scientific", vec_flt, strings_sci_flt); + test_boost_spirit_qi("Boost.Spirit.Qi double scientific", vec_dbl, strings_sci_dbl); + + test_from_chars("std::from_chars float scientific", vec_flt, strings_sci_flt); + test_from_chars("std::from_chars double scientific", vec_dbl, strings_sci_dbl); + + test_boost_from_chars("Boost.Charconv::from_chars float scientific", vec_flt, strings_sci_flt); + test_boost_from_chars("Boost.Charconv::from_chars double scientific", vec_dbl, strings_sci_dbl); + + test_google_double_conversion_from_chars("double-conversion float scientific", vec_flt, strings_sci_flt); + test_google_double_conversion_from_chars("double-conversion double scientific", vec_dbl, strings_sci_dbl); + + //test_strtox("std::strtof float hex", vec_flt, strings_hex_flt); + //test_strtox("std::strtod double hex", vec_dbl, strings_hex_dbl); + + //test_boost_from_chars_parser("Boost.Charconv::from_chars::parser float scientific", vec_flt, strings_sci_flt); + //test_boost_from_chars_parser("Boost.Charconv::from_chars::parser double scientific", vec_dbl, strings_sci_dbl); + + //test_from_chars("std::from_chars float hex", vec_flt, erase_0x(strings_hex_flt)); + //test_from_chars("std::from_chars double hex", vec_dbl, erase_0x(strings_hex_dbl)); + //test_boost_from_chars("Boost.Charconv::from_chars float hex", vec_flt, erase_0x(strings_hex_flt)); + //test_boost_from_chars("Boost.Charconv::from_chars double hex", vec_dbl, erase_0x(strings_hex_dbl)); + + puts("\n-----from_chars int-----"); + + test_strtox_integer<10>("std::strtoul uint32_t", vec_u32, strings_u32); + test_strtox_integer<10>("std::strtoull uint64_t", vec_u64, strings_u64); + + test_boost_lexical_cast_parse("Boost.lexical_cast uint32_t", vec_u32, strings_u32); + test_boost_lexical_cast_parse("Boost.lexical_cast uint64_t", vec_u64, strings_u64); + + test_boost_spirit_qi("Boost.Spirit.Qi uint32_t", vec_u32, strings_u32); + test_boost_spirit_qi("Boost.Spirit.Qi uint64_t", vec_u64, strings_u64); + + test_from_chars_integer<10>("std::from_chars uint32_t", vec_u32, strings_u32); + test_from_chars_integer<10>("std::from_chars uint64_t", vec_u64, strings_u64); + + test_boost_from_chars_integer<10>("Boost.Charconv::from_chars uint32_t", vec_u32, strings_u32); + test_boost_from_chars_integer<10>("Boost.Charconv::from_chars uint64_t", vec_u64, strings_u64); + + printf("global_dummy: %u\n", global_dummy); +} + +int main() +{ + try { + test_all(); + } catch (const exception& e) { + printf("Exception: %s\n", e.what()); + } catch (...) { + printf("Unknown exception.\n"); + } + + return 1; +} + +#else + +int main() +{ + std::cerr << "Benchmarks not run" << std::endl; + return 1; +} + +#endif