// (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 #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 using namespace std; using namespace std::chrono; void verify(const bool b) { if (!b) { puts("VERIFICATION FAILURE"); exit(EXIT_FAILURE); } } enum class RoundTrip { Sci, Fix, Gen, Hex, Lossy }; constexpr size_t N = 2'000'000; // how many floating-point values to test constexpr size_t K = 5; // how many times to repeat the test, for cleaner timing constexpr size_t BufSize = 2'000; // more than enough unsigned int global_dummy = 0; template void test_boost_spirit_karma(const char* const str, const vector& vec) { namespace karma = boost::spirit::karma; 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, karma::float_, elem); } else { karma::generate(it, karma::double_, elem); } } } const auto finish = steady_clock::now(); printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); } 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); } 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) { const int 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) { 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 { verify(strtod(buf, nullptr) == 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 { Floating round_trip; const auto 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 { Floating round_trip; const auto 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; } } } } #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 { static_assert(RT == RoundTrip::Hex); if constexpr (is_same_v) { ret = sprintf_wrapper(buf, "%.6a", elem); } else { ret = sprintf_wrapper(buf, "%.13a", 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); } #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); } #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 + libc++"; #elif !defined(__clang__) && defined(__APPLE__) && defined(__arm64__) const char* const toolset = "GCC Apple ARM + libstdc++"; #else const char* const toolset = "Unknown Toolset"; #endif puts(toolset); vector vec_flt; vector vec_dbl; { 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); } } 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 float plain shortest", vec_flt); test_boost_to_chars("Boost.Charconv 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_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_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_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_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_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_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_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); #endif // AVOID_CHARCONV puts("----------"); 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); test_strtox("std::strtof float scientific", vec_flt, strings_sci_flt); test_strtox("std::strtod 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); #ifndef AVOID_CHARCONV 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_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)); #endif // AVOID_CHARCONV printf("global_dummy: %u\n", global_dummy); } #ifdef BOOST_CHARCONV_RUN_BENCHMARKS int main() { try { test_all(); } catch (const exception& e) { printf("Exception: %s\n", e.what()); } catch (...) { printf("Unknown exception.\n"); } assert(1 == 0); } #else int main() { return 0; } #endif