diff --git a/doc/charconv.adoc b/doc/charconv.adoc index 022fd17..c652ce7 100644 --- a/doc/charconv.adoc +++ b/doc/charconv.adoc @@ -18,6 +18,7 @@ Peter Dimov and Matt Borland include::charconv/overview.adoc[] include::charconv/from_chars.adoc[] +include::charconv/to_chars.adoc[] include::charconv/reference.adoc[] include::charconv/copyright.adoc[] diff --git a/doc/charconv.html b/doc/charconv.html deleted file mode 100644 index 5fc9ee3..0000000 --- a/doc/charconv.html +++ /dev/null @@ -1,981 +0,0 @@ - - - - - - - - -CharConv: An Implementation of <charconv> in C++11 - - - - - - -
-
-

Overview

-
-
-

Description

-
-

Charconv is a collection of parsing functions that are locale-independent, non-allocating, and non-throwing.

-
-
-
-

Usage Examples

-
-
-
#include <boost/charconv.hpp>
-
-const char* buffer = "42";
-int v = 0;
-auto r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v);
-assert(r == 42);
-
-
-
-
-

Supported Compilers

-
-
    -
  • -

    GCC 5 or later with -std=c++11 or above

    -
  • -
  • -

    Clang 3.9 or later with -std=c++11 or above

    -
  • -
  • -

    Visual Studio 2015, 2017, 2019

    -
  • -
-
-
-

Tested on Github Actions.

-
-
-
-
-
-

from_chars

-
-
-

from_chars overview

-
-
-
struct from_chars_result
-{
-    const char* ptr;
-    int ec;
-
-    friend constexpr bool operator==(const from_chars_result& lhs, const from_chars_result& rhs) noexcept
-    friend constexpr bool operator!=(const from_chars_result& lhs, const from_chars_result& rhs) noexcept
-}
-
-template <typename Integral>
-BOOST_CXX14_CONSTEXPR from_chars_result from_chars(const char* first, const char* last, Integral& value, int base = 10) noexcept;
-
-BOOST_CXX14_CONSTEXPR from_chars_result from_chars<bool>(const char* first, const char* last, bool& value, int base) = delete;
-
-
-
-
-

from_chars_result

-
-
    -
  • -

    ptr - points to the first character not matching the pattern, or has the value of last if all characters are successfully parsed.

    -
  • -
  • -

    ec - the error code. Valid values for <cerrno> are:

    -
    -
      -
    • -

      0 - successful parsing

      -
    • -
    • -

      EINVAL - invalid argument (e.g. parsing a negative number into an unsigned type)

      -
    • -
    • -

      ERANGE - 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

    -
  • -
-
-
-
-

from_chars

-
-
    -
  • -

    first, last - valid range to parse

    -
  • -
  • -

    value - where the output is stored upon successful parsing

    -
  • -
  • -

    base - the integer base to use. Must be between 2 and 36 inclusive

    -
  • -
  • -

    from_chars for integral types is constexpr when compiled using -std=c++14 or newer

    -
    -
      -
    • -

      One known exception is GCC 5 which does not support constexpr comparison of const char*.

      -
    • -
    -
    -
  • -
-
-
-
-

from_chars for integral types

-
-
    -
  • -

    All built-in integral types are allowed except bool which is deleted

    -
  • -
  • -

    These function have been tested to support int128 and unsigned int128 when compiling with -std=gnu++11 or newer

    -
  • -
-
-
-
-

Examples

-
-

Basic usage

-
-
-
const char* buffer = "42";
-int v = 0;
-from_chars_result r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v);
-assert(r.ec == 0);
-assert(v == 42);
-
-
-
-
-

Hexadecimal

-
-
-
const char* buffer = "2a";
-unsigned v = 0;
-auto r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v, 16);
-assert(r.ec == 0);
-assert(v == 42);
-
-
-
-
-

EINVAL

-
-
-
const char* buffer = "-123";
-unsigned v = 0;
-auto r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v);
-assert(r.ec, EINVAL);
-assert(v == 0);
-
-
-
-

In the event of EINVAL v is not set by from_chars

-
-
-
-

ERANGE

-
-
-
const char* buffer = "1234";
-unsigned char v = 0;
-auto r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v);
-assert(r.ec == ERANGE);
-assert(v == 0)
-
-
-
-

In the event of ERANGE v is not set by from_chars

-
-
-
-
-
-
-

Reference

-
-
-

<boost/charconv/from_chars.hpp>

-
-

Synopsis

-
-
-
namespace boost {
-namespace charconv {
-
-struct from_chars_result;
-
-template <typename Integral>
-BOOST_CXX14_CONSTEXPR from_chars_result from_chars(const char* first, const char* last, Integral& value, int base = 10) noexcept;
-
-// ...
-
-} // namespace charconv
-} // namespace boost
-
-
-
-
-
-

<boost/charconv/to_chars.hpp>

-
-

Synopsis

-
-
-
namespace boost {
-namespace charconv {
-
-struct to_chars_result;
-
-template<class Integral>
-to_chars_result to_chars( char* first, char* last, Integral value, int base = 10 );
-
-// ...
-
-} // namespace charconv
-} // namespace boost
-
-
-
-
-
-

<boost/charconv.hpp>

-
-

This convenience header includes all headers previously -mentioned.

-
-
-
-
-
- -
-
-

This documentation is copyright 2022-2023 Peter Dimov and Matt Borland and is distributed under -the Boost Software License, Version 1.0.

-
-
-
-
- - - - \ No newline at end of file diff --git a/doc/charconv/overview.adoc b/doc/charconv/overview.adoc index 7c14230..20c5bcd 100644 --- a/doc/charconv/overview.adoc +++ b/doc/charconv/overview.adoc @@ -13,15 +13,21 @@ https://www.boost.org/LICENSE_1_0.txt Charconv is a collection of parsing functions that are locale-independent, non-allocating, and non-throwing. == Usage Examples - -``` +[source, c++] +---- #include const char* buffer = "42"; int v = 0; auto r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v); assert(r == 42); -``` + +char buffer[64]; +int v = 12345; +auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer) - 1, v); +assert(!strcmp(buffer, "123456")); // Strcmp returns 0 on match + +---- == Supported Compilers diff --git a/doc/charconv/reference.adoc b/doc/charconv/reference.adoc index 1e7c89b..d3c6aad 100644 --- a/doc/charconv/reference.adoc +++ b/doc/charconv/reference.adoc @@ -1,5 +1,6 @@ //// Copyright 2022 Peter Dimov +Copyright 2023 Matt Borland Distributed under the Boost Software License, Version 1.0. https://www.boost.org/LICENSE_1_0.txt //// @@ -11,8 +12,8 @@ https://www.boost.org/LICENSE_1_0.txt == === Synopsis - -``` +[source, c++] +---- namespace boost { namespace charconv { @@ -25,26 +26,26 @@ BOOST_CXX14_CONSTEXPR from_chars_result from_chars(const char* first, const char } // namespace charconv } // namespace boost -``` +---- == === Synopsis - -``` +[source, c++] +---- namespace boost { namespace charconv { struct to_chars_result; -template -to_chars_result to_chars( char* first, char* last, Integral value, int base = 10 ); +template +BOOST_CHARCONV_CONSTEXPR to_chars_result to_chars( char* first, char* last, Integral value, int base = 10 ) noexcept; // ... } // namespace charconv } // namespace boost -``` +---- == diff --git a/doc/charconv/to_chars.adoc b/doc/charconv/to_chars.adoc new file mode 100644 index 0000000..f162052 --- /dev/null +++ b/doc/charconv/to_chars.adoc @@ -0,0 +1,77 @@ +//// +Copyright 2023 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + += to_chars +:idprefix: to_chars_ + +== to_chars overview +[source, c++] +---- +struct to_chars_result +{ + char* ptr; + int ec; + + friend constexpr bool operator==(const to_chars_result& lhs, const to_chars_result& rhs) noexcept; + friend constexpr bool operator!=(const to_chars_result& lhs, const to_chars_result& rhs) noexcept; +}; + +template +BOOST_CHARCONV_CONSTEXPR to_chars_result(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; +---- + +== to_chars_result +* ptr - points to the first character +* ec - the error code. Valid values from are: +** 0 - successful parsing +** EINVAL - invalid argument +** ERANGE - result out of range (e.g. overflow) +* operator== - compares the value of ptr and ec for equality +* operator!= - compares the value of ptr and ec for inequality + +== to_chars +* first, last - pointers to the character buffer +* value - the value to be paresed into the buffer +* base - the integer base to use. Must be between 2 and 36 inclusive +* 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` + +== to_chars for integral types +* All built-in integral types are allowed except bool which is deleted + +== Examples + +=== Basic Usage +[source, c++] +---- +char buffer[64] {}; +int v = 42; +to_chars_result r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer) - 1, v); +assert(r.ec == 0); +assert(!strcmp(buffer, "42")); // strcmp returns 0 on match +---- + +=== Hexadecimal +[source, c++] +---- +char buffer[64] {}; +int v = 42; +to_chars_result r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer) - 1, v, 16); +assert(r.ec == 0); +assert(!strcmp(buffer, "2a")); // strcmp returns 0 on match +---- + +=== ERANGE +[source, c++] +---- +char buffer[3] {}; +int v = -1234; +to_chars_result r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer) - 1, v, 16); +assert(r.ec == ERANGE); +---- +In the event of ERANGE to_chars_result.ptr is first diff --git a/include/boost/charconv/detail/apply_sign.hpp b/include/boost/charconv/detail/apply_sign.hpp new file mode 100644 index 0000000..244c0bb --- /dev/null +++ b/include/boost/charconv/detail/apply_sign.hpp @@ -0,0 +1,29 @@ +// Copyright 2023 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_CHARCONV_DETAIL_APPLY_SIGN_HPP +#define BOOST_CHARCONV_DETAIL_APPLY_SIGN_HPP + +// Workaround for warning C4146: unary minus operator applied to unsigned type, result still unsigned +// Occurs using MSVC with pre-C++17 language standards + +#include + +namespace boost { namespace charconv { namespace detail { + +template ::value, bool>::type = true> +constexpr Integer apply_sign(Integer val) noexcept +{ + return -val; +} + +template ::value, bool>::type = true> +constexpr Integer apply_sign(Integer val) noexcept +{ + return val; +} + +}}} // Namespaces + +#endif // BOOST_CHARCONV_DETAIL_APPLY_SIGN_HPP diff --git a/include/boost/charconv/detail/integer_conversion.hpp b/include/boost/charconv/detail/integer_conversion.hpp new file mode 100644 index 0000000..6ddc7d3 --- /dev/null +++ b/include/boost/charconv/detail/integer_conversion.hpp @@ -0,0 +1,45 @@ +// Copyright 2023 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_CHARCONV_DETAIL_CONCATENATE_HPP +#define BOOST_CHARCONV_DETAIL_CONCATENATE_HPP + +#include +#include +#include +#include + +namespace boost { namespace charconv { namespace detail { + +constexpr std::uint64_t pack(std::uint32_t word1, std::uint32_t word2) noexcept +{ + return static_cast(word1) << 32 | word2; +} + +BOOST_CXX14_CONSTEXPR std::pair unpack(std::uint64_t value) +{ + auto x = static_cast(value >> UINT64_C(32)); + auto y = static_cast(value & UINT32_MAX); + + return std::make_pair(x, y); +} + +#ifdef __GLIBCXX_TYPE_INT_N_0 +constexpr unsigned __int128 pack(std::uint64_t word1, std::uint64_t word2) noexcept +{ + return static_cast(word1) << 64 | word2; +} + +BOOST_CXX14_CONSTEXPR std::pair unpack(unsigned __int128 value) +{ + auto x = static_cast(value >> 64); + auto y = static_cast(value); + + return std::make_pair(x, y); +} +#endif // defined(__GLIBCXX_TYPE_INT_N_0) + +}}} // Namespaces + +#endif // BOOST_CHARCONV_DETAIL_CONCATENATE_HPP diff --git a/include/boost/charconv/detail/integer_search_trees.hpp b/include/boost/charconv/detail/integer_search_trees.hpp new file mode 100644 index 0000000..30250de --- /dev/null +++ b/include/boost/charconv/detail/integer_search_trees.hpp @@ -0,0 +1,239 @@ +// Copyright 2023 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_CHARCONV_DETAIL_INTEGER_SEARCH_TREES_HPP +#define BOOST_CHARCONV_DETAIL_INTEGER_SEARCH_TREES_HPP + +// https://stackoverflow.com/questions/1489830/efficient-way-to-determine-number-of-digits-in-an-integer?page=1&tab=scoredesc#tab-top +// https://graphics.stanford.edu/~seander/bithacks.html + +#include "../config.hpp" +#include +#include +#include + +namespace boost { namespace charconv { namespace detail { + +// Generic solution +template +BOOST_CXX14_CONSTEXPR int num_digits(T x) noexcept +{ + int digits = 0; + + while (x) + { + x /= 10; + ++digits; + } + + return digits; +} + +template <> +BOOST_CXX14_CONSTEXPR int num_digits(std::uint32_t x) noexcept +{ + if (x >= UINT32_C(10000)) + { + if (x >= UINT32_C(10000000)) + { + if (x >= UINT32_C(100000000)) + { + if (x >= UINT32_C(1000000000)) + { + return 10; + } + return 9; + } + return 8; + } + + else if (x >= UINT32_C(100000)) + { + if (x >= UINT32_C(1000000)) + { + return 7; + } + return 6; + } + return 5; + } + else if (x >= UINT32_C(100)) + { + if (x >= UINT32_C(1000)) + { + return 4; + } + return 3; + } + else if (x >= UINT32_C(10)) + { + return 2; + } + + return 1; +} + +template <> +BOOST_CXX14_CONSTEXPR int num_digits(std::uint64_t x) noexcept +{ + if (x >= UINT64_C(10000000000)) + { + if (x >= UINT64_C(100000000000000)) + { + if (x >= UINT64_C(10000000000000000)) + { + if (x >= UINT64_C(100000000000000000)) + { + if (x >= UINT64_C(1000000000000000000)) + { + if (x >= UINT64_C(10000000000000000000)) + { + return 20; + } + return 19; + } + return 18; + } + return 17; + } + else if (x >= UINT64_C(1000000000000000)) + { + return 16; + } + return 15; + } + if (x >= UINT64_C(1000000000000)) + { + if (x >= UINT64_C(10000000000000)) + { + return 14; + } + return 13; + } + if (x >= UINT64_C(100000000000)) + { + return 12; + } + return 11; + } + else if (x >= UINT64_C(100000)) + { + if (x >= UINT64_C(10000000)) + { + if (x >= UINT64_C(100000000)) + { + if (x >= UINT64_C(1000000000)) + { + return 10; + } + return 9; + } + return 8; + } + if (x >= UINT64_C(1000000)) + { + return 7; + } + return 6; + } + if (x >= UINT64_C(100)) + { + if (x >= UINT64_C(1000)) + { + if (x >= UINT64_C(10000)) + { + return 5; + } + return 4; + } + return 3; + } + if (x >= UINT64_C(10)) + { + return 2; + } + return 1; +} + +#ifdef __GLIBCXX_TYPE_INT_N_0 +static constexpr std::array powers_of_10 = +{{ + UINT64_C(1), UINT64_C(10), UINT64_C(100), UINT64_C(1000), UINT64_C(10000), UINT64_C(100000), UINT64_C(1000000), + UINT64_C(10000000), UINT64_C(100000000), UINT64_C(1000000000), UINT64_C(10000000000), UINT64_C(100000000000), + UINT64_C(1000000000000), UINT64_C(10000000000000), UINT64_C(100000000000000), UINT64_C(1000000000000000), + UINT64_C(10000000000000000), UINT64_C(100000000000000000), UINT64_C(1000000000000000000), UINT64_C(10000000000000000000) +}}; + +// Assume that if someone is using 128 bit ints they are favoring the top end of the range +// Max value is 340,282,366,920,938,463,463,374,607,431,768,211,455 (39 digits) +BOOST_CXX14_CONSTEXPR int num_digits(unsigned __int128 x) noexcept +{ + // There is not literal for unsigned __int128 so we need to calculate them using the max value of the + // std::uint64_t powers of 10 + constexpr unsigned __int128 digits_39 = static_cast(UINT64_C(10000000000000000000)) * + static_cast(UINT64_C(1000000000000000000)); + constexpr unsigned __int128 digits_38 = digits_39 / 10; + constexpr unsigned __int128 digits_37 = digits_38 / 10; + constexpr unsigned __int128 digits_36 = digits_37 / 10; + constexpr unsigned __int128 digits_35 = digits_36 / 10; + constexpr unsigned __int128 digits_34 = digits_35 / 10; + constexpr unsigned __int128 digits_33 = digits_34 / 10; + constexpr unsigned __int128 digits_32 = digits_33 / 10; + constexpr unsigned __int128 digits_31 = digits_32 / 10; + constexpr unsigned __int128 digits_30 = digits_31 / 10; + constexpr unsigned __int128 digits_29 = digits_30 / 10; + constexpr unsigned __int128 digits_28 = digits_29 / 10; + constexpr unsigned __int128 digits_27 = digits_28 / 10; + constexpr unsigned __int128 digits_26 = digits_27 / 10; + constexpr unsigned __int128 digits_25 = digits_26 / 10; + constexpr unsigned __int128 digits_24 = digits_25 / 10; + constexpr unsigned __int128 digits_23 = digits_24 / 10; + constexpr unsigned __int128 digits_22 = digits_23 / 10; + constexpr unsigned __int128 digits_21 = digits_22 / 10; + + return (x > digits_39) ? 39 : + (x > digits_38) ? 38 : + (x > digits_37) ? 37 : + (x > digits_36) ? 36 : + (x > digits_35) ? 35 : + (x > digits_34) ? 34 : + (x > digits_33) ? 33 : + (x > digits_32) ? 32 : + (x > digits_31) ? 31 : + (x > digits_30) ? 30 : + (x > digits_29) ? 29 : + (x > digits_28) ? 28 : + (x > digits_27) ? 27 : + (x > digits_26) ? 26 : + (x > digits_25) ? 25 : + (x > digits_24) ? 24 : + (x > digits_23) ? 23 : + (x > digits_22) ? 22 : + (x > digits_21) ? 21 : + (x > powers_of_10[19]) ? 20 : + (x > powers_of_10[18]) ? 19 : + (x > powers_of_10[17]) ? 18 : + (x > powers_of_10[16]) ? 17 : + (x > powers_of_10[15]) ? 16 : + (x > powers_of_10[14]) ? 15 : + (x > powers_of_10[13]) ? 14 : + (x > powers_of_10[12]) ? 13 : + (x > powers_of_10[11]) ? 12 : + (x > powers_of_10[10]) ? 11 : + (x > powers_of_10[9]) ? 10 : + (x > powers_of_10[8]) ? 9 : + (x > powers_of_10[7]) ? 8 : + (x > powers_of_10[6]) ? 7 : + (x > powers_of_10[5]) ? 6 : + (x > powers_of_10[4]) ? 5 : + (x > powers_of_10[3]) ? 4 : + (x > powers_of_10[2]) ? 3 : + (x > powers_of_10[1]) ? 2 : + (x > powers_of_10[0]) ? 1 : 0; +} +#endif // 128-bit support + +}}} // Namespace boost::charconv::detail + +#endif // BOOST_CHARCONV_DETAIL_INTEGER_SEARCH_TREES_HPP diff --git a/include/boost/charconv/detail/is_constant_evaluated.hpp b/include/boost/charconv/detail/is_constant_evaluated.hpp new file mode 100644 index 0000000..046c47a --- /dev/null +++ b/include/boost/charconv/detail/is_constant_evaluated.hpp @@ -0,0 +1,49 @@ +// Copyright John Maddock 2011-2021. +// 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) + +#ifndef BOOST_CHARCONV_TOOLS_IS_CONSTANT_EVALUATED_HPP +#define BOOST_CHARCONV_TOOLS_IS_CONSTANT_EVALUATED_HPP + +#include +#include + +#ifdef __cpp_lib_is_constant_evaluated +# define BOOST_CHARCONV_HAS_IS_CONSTANT_EVALUATED +#endif + +#ifdef __has_builtin +# if __has_builtin(__builtin_is_constant_evaluated) && !defined(BOOST_NO_CXX14_CONSTEXPR) +# define BOOST_CHARCONV_HAS_BUILTIN_IS_CONSTANT_EVALUATED +# endif +#endif + +// +// MSVC also supports __builtin_is_constant_evaluated if it's recent enough: +// +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 192528326) +# define BOOST_CHARCONV_HAS_BUILTIN_IS_CONSTANT_EVALUATED +#endif + +// +// As does GCC-9: +// +#if !defined(BOOST_NO_CXX14_CONSTEXPR) && (__GNUC__ >= 9) && !defined(BOOST_CHARCONV_HAS_BUILTIN_IS_CONSTANT_EVALUATED) +# define BOOST_CHARCONV_HAS_BUILTIN_IS_CONSTANT_EVALUATED +#endif + +#if defined(BOOST_CHARCONV_HAS_IS_CONSTANT_EVALUATED) && !defined(BOOST_NO_CXX14_CONSTEXPR) +# define BOOST_CHARCONV_IS_CONSTANT_EVALUATED(x) std::is_constant_evaluated() +#elif defined(BOOST_CHARCONV_HAS_BUILTIN_IS_CONSTANT_EVALUATED) +# define BOOST_CHARCONV_IS_CONSTANT_EVALUATED(x) __builtin_is_constant_evaluated() +#elif !defined(BOOST_NO_CXX14_CONSTEXPR) && (__GNUC__ >= 6) +# define BOOST_CHARCONV_IS_CONSTANT_EVALUATED(x) __builtin_constant_p(x) +# define BOOST_CHARCONV_USING_BUILTIN_CONSTANT_P +#else +# define BOOST_CHARCONV_IS_CONSTANT_EVALUATED(x) false +# define BOOST_CHARCONV_NO_CONSTEXPR_DETECTION +#endif + +#endif // BOOST_CHARCONV_TOOLS_IS_CONSTANT_EVALUATED_HPP diff --git a/include/boost/charconv/detail/memcpy.hpp b/include/boost/charconv/detail/memcpy.hpp new file mode 100644 index 0000000..7509142 --- /dev/null +++ b/include/boost/charconv/detail/memcpy.hpp @@ -0,0 +1,78 @@ +// Copyright 2023 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_CHARCONV_DETAIL_MEMCPY_HPP +#define BOOST_CHARCONV_DETAIL_MEMCPY_HPP + +#include +#include +#include + +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89689 +// GCC 10 added checks for length of memcpy which yields the following warning (converted to error with -Werror) +// /usr/include/x86_64-linux-gnu/bits/string_fortified.h:34:33: error: +// ‘void* __builtin___memcpy_chk(void*, const void*, long unsigned int, long unsigned int)’ specified size between +// 18446744071562067968 and 18446744073709551615 exceeds maximum object size 9223372036854775807 [-Werror=stringop-overflow=] +// +// memcpy is defined as taking a size_t for the count and the largest count this will recieve is the number of digits +// in a 128-bit int (39) so we can safely ignore +#if __GNUC__ >= 10 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wstringop-overflow" +# define BOOST_CHARCONV_STRINGOP_OVERFLOW_DISABLED +#endif + +namespace boost { namespace charconv { namespace detail { + +#if !defined(BOOST_CHARCONV_NO_CONSTEXPR_DETECTION) && defined(BOOST_CXX14_CONSTEXPR) + +#define BOOST_CHARCONV_CONSTEXPR constexpr + +constexpr char* memcpy(char* dest, const char* src, std::size_t count) +{ + if (BOOST_CHARCONV_IS_CONSTANT_EVALUATED(count)) + { + for (std::size_t i = 0; i < count; ++i) + { + *(dest + i) = *(src + i); + } + + return dest; + } + else + { + // Workaround for GCC-11 because it does not honor GCC diagnostic ignored + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431 + // Hopefully the optimizer turns this into memcpy + #if __GNUC__ == 11 + for (std::size_t i = 0; i < count; ++i) + { + *(dest + i) = *(src + i); + } + + return dest; + #else + return static_cast(std::memcpy(dest, src, count)); + #endif + } +} + +#else // Either not C++14 or no way of telling if we are in a constexpr context + +#define BOOST_CHARCONV_CONSTEXPR + +inline void* memcpy(void* dest, const void* src, std::size_t count) +{ + return std::memcpy(dest, src, count); +} + +#endif + +}}} // Namespace boost::charconv::detail + +#ifdef BOOST_CHARCONV_STRINGOP_OVERFLOW_DISABLED +# pragma GCC diagnostic pop +#endif + +#endif // BOOST_CHARCONV_DETAIL_MEMCPY_HPP diff --git a/include/boost/charconv/from_chars.hpp b/include/boost/charconv/from_chars.hpp index 9e5fcc6..4f8f040 100644 --- a/include/boost/charconv/from_chars.hpp +++ b/include/boost/charconv/from_chars.hpp @@ -6,6 +6,7 @@ #ifndef BOOST_CHARCONV_FROM_CHARS_HPP_INCLUDED #define BOOST_CHARCONV_FROM_CHARS_HPP_INCLUDED +#include #include #include #include @@ -67,18 +68,6 @@ constexpr unsigned char digit_from_char(char val) noexcept return uchar_values[static_cast(val)]; } -template ::value, bool>::type = true> -constexpr Integer apply_sign(Integer val) noexcept -{ - return -val; -} - -template ::value, bool>::type = true> -constexpr Integer apply_sign(Integer val) noexcept -{ - return val; -} - template BOOST_CXX14_CONSTEXPR boost::charconv::from_chars_result from_chars_integer_impl(const char* first, const char* last, Integer& value, int base) noexcept { diff --git a/include/boost/charconv/to_chars.hpp b/include/boost/charconv/to_chars.hpp index 5362971..b720966 100644 --- a/include/boost/charconv/to_chars.hpp +++ b/include/boost/charconv/to_chars.hpp @@ -1,12 +1,26 @@ // Copyright 2022 Peter Dimov // Copyright 2023 Matt Borland +// Copyright 2023 Junekey Jeon // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt #ifndef BOOST_CHARCONV_TO_CHARS_HPP_INCLUDED #define BOOST_CHARCONV_TO_CHARS_HPP_INCLUDED +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace boost { namespace charconv { @@ -15,19 +29,346 @@ namespace boost { namespace charconv { struct to_chars_result { char* ptr; + + // Values: + // 0 = no error + // EINVAL = invalid_argument + // ERANGE = result_out_of_range int ec; - friend bool operator==(const to_chars_result& lhs, const to_chars_result& rhs) + + constexpr friend bool operator==(const to_chars_result& lhs, const to_chars_result& rhs) noexcept { return lhs.ptr == rhs.ptr && lhs.ec == rhs.ec; } - friend bool operator!=(const to_chars_result& lhs, const to_chars_result& rhs) + constexpr friend bool operator!=(const to_chars_result& lhs, const to_chars_result& rhs) noexcept { return !(lhs == rhs); } }; -BOOST_CHARCONV_DECL to_chars_result to_chars(char* first, char* last, int value, int base = 10); +namespace detail { + + static constexpr char radix_table[] = { + '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', + '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', + '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', + '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', + '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', + '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', + '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', + '3', '5', '3', '6', '3', '7', '3', '8', '3', '9', + '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', + '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', + '5', '0', '5', '1', '5', '2', '5', '3', '5', '4', + '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', + '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', + '6', '5', '6', '6', '6', '7', '6', '8', '6', '9', + '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', + '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', + '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', + '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', + '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', + '9', '5', '9', '6', '9', '7', '9', '8', '9', '9' + }; + + static constexpr char digit_table[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z' + }; + +// See: https://jk-jeon.github.io/posts/2022/02/jeaiii-algorithm/ +// https://arxiv.org/abs/2101.11408 +BOOST_CHARCONV_CONSTEXPR char* decompose32(std::uint32_t value, char* buffer) noexcept +{ + constexpr auto mask = (std::uint64_t(1) << 57) - 1; + auto y = value * std::uint64_t(1441151881); + + for (std::size_t i {}; i < 10; i += 2) + { + boost::charconv::detail::memcpy(buffer + i, radix_table + static_cast(y >> 57) * 2, 2); + y &= mask; + y *= 100; + } + + return buffer + 10; +} + +#ifdef BOOST_MSVC +# pragma warning(push) +# pragma warning(disable: 4127) +#endif + +template +BOOST_CHARCONV_CONSTEXPR to_chars_result to_chars_integer_impl(char* first, char* last, Integer value) noexcept +{ + using Unsigned_Integer = typename std::make_unsigned::type; + Unsigned_Integer unsigned_value {}; + + char buffer[10] {}; + int converted_value_digits {}; + const std::ptrdiff_t user_buffer_size = last - first; + BOOST_ATTRIBUTE_UNUSED bool is_negative = false; + + if (!(first <= last)) + { + return {last, EINVAL}; + } + + // Strip the sign from the value and apply at the end after parsing if the type is signed + BOOST_IF_CONSTEXPR (std::is_signed::value) + { + if (value < 0) + { + is_negative = true; + unsigned_value = apply_sign(value); + } + else + { + unsigned_value = value; + } + } + else + { + unsigned_value = value; + } + + // If the type is less than 32 bits we can use this without change + // If the type is greater than 32 bits we use a binary search tree to figure out how many digits + // are present and then decompose the value into two (or more) std::uint32_t of known length so that we + // don't have the issue of removing leading zeros from the least significant digits + + // Yields: warning C4127: conditional expression is constant becuase first half of the expression is constant + // but we need to short circuit to avoid UB on the second half + if (std::numeric_limits::digits <= std::numeric_limits::digits || + unsigned_value <= static_cast((std::numeric_limits::max)())) + { + const auto converted_value = static_cast(unsigned_value); + converted_value_digits = num_digits(converted_value); + + if (converted_value_digits > user_buffer_size) + { + return {last, EOVERFLOW}; + } + + decompose32(converted_value, buffer); + + if (is_negative) + { + *first++ = '-'; + } + + boost::charconv::detail::memcpy(first, buffer + (sizeof(buffer) - converted_value_digits), converted_value_digits); + } + else if (std::numeric_limits::digits <= std::numeric_limits::digits || + static_cast(unsigned_value) <= (std::numeric_limits::max)()) + { + auto converted_value = static_cast(unsigned_value); + converted_value_digits = num_digits(converted_value); + + if (converted_value_digits > user_buffer_size) + { + return {last, EOVERFLOW}; + } + + if (is_negative) + { + *first++ = '-'; + } + + // Only store 9 digits in each to avoid overflow + if (num_digits(converted_value) <= 18) + { + const auto x = static_cast(converted_value / UINT64_C(1000000000)); + const auto y = static_cast(converted_value % UINT64_C(1000000000)); + const int first_value_chars = num_digits(x); + + decompose32(x, buffer); + boost::charconv::detail::memcpy(first, buffer + (sizeof(buffer) - first_value_chars), first_value_chars); + + decompose32(y, buffer); + boost::charconv::detail::memcpy(first + first_value_chars, buffer + 1, sizeof(buffer) - 1); + } + else + { + const auto x = static_cast(converted_value / UINT64_C(100000000000)); + converted_value -= x * UINT64_C(100000000000); + const auto y = static_cast(converted_value / UINT64_C(100)); + const auto z = static_cast(converted_value % UINT64_C(100)); + + if (converted_value_digits == 19) + { + decompose32(x, buffer); + boost::charconv::detail::memcpy(first, buffer + 2, sizeof(buffer) - 2); + + decompose32(y, buffer); + boost::charconv::detail::memcpy(first + 8, buffer + 1, sizeof(buffer) - 1); + + decompose32(z, buffer); + boost::charconv::detail::memcpy(first + 17, buffer + 8, 2); + } + else // 20 + { + decompose32(x, buffer); + boost::charconv::detail::memcpy(first, buffer + 1, sizeof(buffer) - 1); + + decompose32(y, buffer); + boost::charconv::detail::memcpy(first + 9, buffer + 1, sizeof(buffer) - 1); + + decompose32(z, buffer); + boost::charconv::detail::memcpy(first + 18, buffer + 8, 2); + } + } + } + #if 0 + // unsigned __128 requires 4 shifts + // Could just recursivly call the uint64_t twice and then compose 2x 64 bits + else if (static_cast(value) <= (std::numeric_limits::max)()) + #endif + else + { + BOOST_CHARCONV_ASSERT_MSG(sizeof(Integer) < 1, "Your type is unsupported. Use a built-in integral type"); + } + + return {first + converted_value_digits, 0}; +} + +#ifdef BOOST_MSVC +# pragma warning(pop) +#endif + +// All other bases +// Use a simple lookup table to put together the Integer in character form +template +BOOST_CHARCONV_CONSTEXPR to_chars_result to_chars_integer_impl(char* first, char* last, Integer value, int base) noexcept +{ + BOOST_CHARCONV_ASSERT_MSG(base >= 2 && base <= 36, "Base must be between 2 and 36 (inclusive)"); + + using Unsigned_Integer = typename std::make_unsigned::type; + + const std::ptrdiff_t output_length = last - first; + + if (!(first <= last)) + { + return {last, EINVAL}; + } + + if (value == 0) + { + *first++ = '0'; + return {first, 0}; + } + + Unsigned_Integer unsigned_value {}; + const auto unsigned_base = static_cast(base); + + BOOST_IF_CONSTEXPR (std::is_signed::value) + { + if (value < 0) + { + *first++ = '-'; + unsigned_value = apply_sign(value); + } + else + { + unsigned_value = value; + } + } + else + { + unsigned_value = value; + } + + constexpr Unsigned_Integer zero = 48U; // Char for '0' + constexpr auto buffer_size = sizeof(Unsigned_Integer) * CHAR_BIT; + char buffer[buffer_size] {}; + const char* buffer_end = buffer + buffer_size; + char* end = buffer + buffer_size - 1; + + // Work from LSB to MSB + switch (base) + { + case 2: + while (unsigned_value != 0) + { + *end-- = static_cast(zero + (unsigned_value & 1U)); // 1<<1 - 1 + unsigned_value >>= 1U; + } + break; + + case 4: + while (unsigned_value != 0) + { + *end-- = static_cast(zero + (unsigned_value & 3U)); // 1<<2 - 1 + unsigned_value >>= 2U; + } + break; + + case 8: + while (unsigned_value != 0) + { + *end-- = static_cast(zero + (unsigned_value & 7U)); // 1<<3 - 1 + unsigned_value >>= 3U; + } + break; + + case 16: + while (unsigned_value != 0) + { + *end-- = digit_table[unsigned_value & 15U]; // 1<<4 - 1 + unsigned_value >>= 4U; + } + break; + + case 32: + while (unsigned_value != 0) + { + *end-- = digit_table[unsigned_value & 31U]; // 1<<5 - 1 + unsigned_value >>= 5U; + } + break; + + default: + while (unsigned_value != 0) + { + *end-- = digit_table[unsigned_value % unsigned_base]; + unsigned_value /= unsigned_base; + } + break; + } + + const std::ptrdiff_t num_chars = buffer_end - end - 1; + + if (num_chars > output_length) + { + return {last, EOVERFLOW}; + } + + boost::charconv::detail::memcpy(first, buffer + (buffer_size - num_chars), num_chars); + + return {first + num_chars, 0}; +} + +} // Namespace detail + +template ::value, bool>::type = true> +BOOST_CHARCONV_CONSTEXPR to_chars_result to_chars(char* first, char* last, Integer value, int base = 10) noexcept +{ + if (base == 10) + { + return detail::to_chars_integer_impl(first, last, value); + } + + return detail::to_chars_integer_impl(first, last, value, base); +} + +template <> +BOOST_CHARCONV_CONSTEXPR to_chars_result to_chars(char* first, char* last, bool value, int base) noexcept = delete; + +// TODO: Not correct, but need to make MSVC happy while working on integers +BOOST_CHARCONV_DECL to_chars_result to_chars(char* first, char* last, float value) noexcept; } // namespace charconv } // namespace boost diff --git a/src/to_chars.cpp b/src/to_chars.cpp index ec0e927..b59936c 100644 --- a/src/to_chars.cpp +++ b/src/to_chars.cpp @@ -7,8 +7,9 @@ #include #include -boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* last, int value, int /*base*/) + +boost::charconv::to_chars_result boost::charconv::to_chars(char* first, char* last, float value) noexcept { - std::snprintf( first, last - first - 1, "%d", value ); - return { first + std::strlen( first ), 0 }; + std::snprintf(first, last - first - 1, "%f", value); + return {first + std::strlen(first), 0}; } diff --git a/test/Jamfile b/test/Jamfile index 5bdc53b..b4ef1b9 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -1,4 +1,5 @@ # Copyright 2022 Peter Dimov +# Copyright 2023 Matt Borland # Distributed under the Boost Software License, Version 1.0. # https://www.boost.org/LICENSE_1_0.txt @@ -20,4 +21,5 @@ run quick.cpp ; run from_chars.cpp ; run to_chars.cpp ; run roundtrip.cpp ; -run from_chars_STL_comp.cpp : : : [ requires cxx17_std_apply ] ; +run from_chars_STL_comp.cpp : : : [ requires cxx17_hdr_charconv ] ; +run to_chars_integer_STL_comp.cpp : : : [ requires cxx17_hdr_charconv ] ; diff --git a/test/to_chars.cpp b/test/to_chars.cpp index 8b9a616..407a15f 100644 --- a/test/to_chars.cpp +++ b/test/to_chars.cpp @@ -5,25 +5,241 @@ #include #include +#include +#include +#include #include +#include + +template +void specific_value_tests(T value) +{ + char buffer[64] {}; + auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer) - 1, value); + BOOST_TEST_EQ(r.ec, 0); + std::string value_string = std::to_string(value); + BOOST_TEST_CSTR_EQ(buffer, value_string.c_str()); +} + +void off_by_one_tests(int value) +{ + char buffer[64] {}; + auto r = boost::charconv::to_chars(buffer, buffer + sizeof(buffer) - 1, value); + BOOST_TEST_EQ(r.ec, 0); + std::string value_string = std::to_string(value); + BOOST_TEST_CSTR_EQ(buffer, value_string.c_str()); +} + +template +void base_thirtytwo_tests() +{ + char buffer1[64] {}; + T v1 = static_cast(42); + auto r1 = boost::charconv::to_chars(buffer1, buffer1 + sizeof(buffer1) - 1, v1, 32); + BOOST_TEST_EQ(r1.ec, 0); + BOOST_TEST_CSTR_EQ(buffer1, "1a"); +} + +template +void base_sixteen_tests() +{ + char buffer1[64] {}; + T v1 = static_cast(42); + auto r1 = boost::charconv::to_chars(buffer1, buffer1 + sizeof(buffer1) - 1, v1, 16); + BOOST_TEST_EQ(r1.ec, 0); + BOOST_TEST_CSTR_EQ(buffer1, "2a"); +} + +template +void base_eight_tests() +{ + char buffer1[64] {}; + T v1 = static_cast(42); + auto r1 = boost::charconv::to_chars(buffer1, buffer1 + sizeof(buffer1) - 1, v1, 8); + BOOST_TEST_EQ(r1.ec, 0); + BOOST_TEST_CSTR_EQ(buffer1, "52"); +} + +template +void base_four_tests() +{ + char buffer1[64] {}; + T v1 = static_cast(42); + auto r1 = boost::charconv::to_chars(buffer1, buffer1 + sizeof(buffer1) - 1, v1, 4); + BOOST_TEST_EQ(r1.ec, 0); + BOOST_TEST_CSTR_EQ(buffer1, "222"); +} + +// Tests the generic implementation +template +void base_30_tests() +{ + char buffer1[64] {}; + T v1 = static_cast(1234); + auto r1 = boost::charconv::to_chars(buffer1, buffer1 + sizeof(buffer1) - 1, v1, 30); + BOOST_TEST_EQ(r1.ec, 0); + BOOST_TEST_CSTR_EQ(buffer1, "1b4"); + + char buffer2[64] {}; + T v2 = static_cast(-4321); + auto r2 = boost::charconv::to_chars(buffer2, buffer2 + sizeof(buffer2) - 1, v2, 30); + BOOST_TEST_EQ(r2.ec, 0); + BOOST_TEST_CSTR_EQ(buffer2, "-4o1"); +} + +template +void overflow_tests() +{ + char buffer1[2] {}; + T v1 = static_cast(250); + auto r1 = boost::charconv::to_chars(buffer1, buffer1 + sizeof(buffer1) - 1, v1); + BOOST_TEST_EQ(r1.ec, EOVERFLOW); + + char buffer2[3] {}; + T v2 = static_cast(12341234); + auto r2 = boost::charconv::to_chars(buffer2, buffer2 + sizeof(buffer2) - 1, v2); + BOOST_TEST_EQ(r2.ec, EOVERFLOW); +} + +template +void base_two_tests() +{ + char buffer1[64] {}; + T v1 = static_cast(42); + auto r1 = boost::charconv::to_chars(buffer1, buffer1 + sizeof(buffer1) - 1, v1, 2); + BOOST_TEST_EQ(r1.ec, 0); + BOOST_TEST_CSTR_EQ(buffer1, "101010"); +} + +template +void sixty_four_bit_tests() +{ + char buffer1[64] {}; + T v1 = static_cast(-1234); + auto r1 = boost::charconv::to_chars(buffer1, buffer1 + sizeof(buffer1) - 1, v1); + BOOST_TEST_EQ(r1.ec, 0); + BOOST_TEST_CSTR_EQ(buffer1, "-1234"); + + char buffer2[64] {}; + T v2 = static_cast(1234123412341234LL); + auto r2 = boost::charconv::to_chars(buffer2, buffer2 + sizeof(buffer2) - 1, v2); + BOOST_TEST_EQ(r2.ec, 0); + BOOST_TEST_CSTR_EQ(buffer2, "1234123412341234"); +} + +template <> +void sixty_four_bit_tests() +{ + char buffer1[64] {}; + std::uint64_t v1 = (std::numeric_limits::max)(); + auto r1 = boost::charconv::to_chars(buffer1, buffer1 + sizeof(buffer1) - 1, v1); + BOOST_TEST_EQ(r1.ec, 0); + BOOST_TEST_CSTR_EQ(buffer1, "18446744073709551615"); + + // Cutting this value in half would overflow a 32 bit unsigned for the back 10 digits + char buffer2[64] {}; + std::uint64_t v2 = UINT64_C(9999999999999999999); + auto r2 = boost::charconv::to_chars(buffer2, buffer2 + sizeof(buffer2) - 1, v2); + BOOST_TEST_EQ(r2.ec, 0); + BOOST_TEST_CSTR_EQ(buffer2, "9999999999999999999"); + + // Account for zeros in the back half of the split + char buffer3[64] {}; + std::uint64_t v3 = UINT64_C(10000000000000000000); + auto r3 = boost::charconv::to_chars(buffer3, buffer3 + sizeof(buffer3) - 1, v3); + BOOST_TEST_EQ(r3.ec, 0); + BOOST_TEST_CSTR_EQ(buffer3, "10000000000000000000"); +} + +template +void negative_vals_test() +{ + char buffer1[10] {}; + T v = -4321; + auto r1 = boost::charconv::to_chars(buffer1, buffer1 + sizeof(buffer1) - 1, v); + BOOST_TEST_EQ(r1.ec, 0); + BOOST_TEST_CSTR_EQ(buffer1, "-4321"); +} + +template +void simple_test() +{ + char buffer1[64] {}; + T v = 34; + auto r1 = boost::charconv::to_chars(buffer1, buffer1 + sizeof(buffer1) - 1, v); + BOOST_TEST_EQ(r1.ec, 0); + BOOST_TEST_CSTR_EQ(buffer1, "34"); + + boost::charconv::to_chars_result r {r1.ptr, r1.ec}; + BOOST_TEST(r1 == r); + + char buffer2[64] {}; + T v2 = 12; + auto r2 = boost::charconv::to_chars(buffer2, buffer2 + sizeof(buffer2) - 1, v2); + BOOST_TEST(r1 != r2); + BOOST_TEST_EQ(r2.ec, 0); + BOOST_TEST_CSTR_EQ(buffer2, "12"); +} int main() { - char buffer[32] = {}; + + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); - int v = 1048576; - auto r = boost::charconv::to_chars( buffer, buffer + sizeof( buffer ) - 1, v ); + negative_vals_test(); + negative_vals_test(); - BOOST_TEST_EQ(r.ec, 0) && BOOST_TEST_CSTR_EQ(buffer, "1048576"); - BOOST_TEST(r == r); + sixty_four_bit_tests(); + sixty_four_bit_tests(); - boost::charconv::to_chars_result r2 {r.ptr, 0}; - BOOST_TEST(r == r2); + // Tests for every specialized base + base_two_tests(); + base_two_tests(); - char buffer2[32] = {}; + base_four_tests(); + base_four_tests(); - auto r3 = boost::charconv::to_chars(buffer2, buffer2 + sizeof(buffer) - 1, v); - BOOST_TEST(r != r3); + base_eight_tests(); + base_eight_tests(); + + base_sixteen_tests(); + base_sixteen_tests(); + + base_thirtytwo_tests(); + base_thirtytwo_tests(); + + // The generic impl + base_30_tests(); + base_30_tests(); + + overflow_tests(); + + // Resulted in off by one errors from random number generation + // Consistently one larger with 10 digit numbers + off_by_one_tests(1159137169); + off_by_one_tests(-1321793318); + off_by_one_tests(2140634902); + + // If we compensate by one these fail + off_by_one_tests(1038882992); + off_by_one_tests(-1065658613); + off_by_one_tests(-1027205339); + + // Fails in CI at the time - Likely overflow when converting to positive + specific_value_tests(-32768); + specific_value_tests(-7061872404794389355L); return boost::report_errors(); } diff --git a/test/to_chars_integer_STL_comp.cpp b/test/to_chars_integer_STL_comp.cpp new file mode 100644 index 0000000..89cdc11 --- /dev/null +++ b/test/to_chars_integer_STL_comp.cpp @@ -0,0 +1,209 @@ +// Copyright 2023 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if !defined(BOOST_NO_CXX17_HDR_CHARCONV) && (!defined(__clang_major__) || (defined(__clang_major__) && __clang_major__ > 7)) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +void stress_test_worker() +{ + std::random_device rd; + std::mt19937_64 gen(rd()); + std::uniform_int_distribution dist((std::numeric_limits::min)(), (std::numeric_limits::max)()); + + std::mt19937_64 base_gen(rd()); + std::uniform_int_distribution base_dist(2, 36); + + for (std::size_t i = 0; i < 100'000'000; ++i) + { + char buffer_stl[128] {}; + char buffer_boost[128] {}; + + T v = dist(gen); + int base = base_dist(base_gen); + + auto r_stl = std::to_chars(buffer_stl, buffer_stl + sizeof(buffer_stl) - 1, v, base); + auto r_boost = boost::charconv::to_chars(buffer_boost, buffer_boost + sizeof(buffer_boost) - 1, v, base); + + BOOST_TEST_EQ(static_cast(r_stl.ptr - buffer_stl), static_cast(r_boost.ptr - buffer_boost)); + BOOST_TEST_CSTR_EQ(buffer_stl, buffer_boost); + } +} + +template +void stress_test() +{ + auto num_threads = std::thread::hardware_concurrency(); + std::vector thread_manager(num_threads); + + for (std::size_t i = 0; i < num_threads; ++i) + { + thread_manager.emplace_back(std::thread(stress_test_worker)); + } + + for (std::size_t i = 0; i < thread_manager.size(); ++i) + { + if (thread_manager[i].joinable()) + { + thread_manager[i].join(); + } + } +} + +template +void random_tests() +{ + std::mt19937_64 gen(42); + std::uniform_int_distribution dist((std::numeric_limits::min)(), (std::numeric_limits::max)()); + + for (std::size_t i = 0; i < 100'000; ++i) + { + char buffer_stl[128] {}; + char buffer_boost[128] {}; + + T v = dist(gen); + + auto r_stl = std::to_chars(buffer_stl, buffer_stl + sizeof(buffer_stl) - 1, v, base); + auto r_boost = boost::charconv::to_chars(buffer_boost, buffer_boost + sizeof(buffer_boost) - 1, v, base); + + BOOST_TEST_EQ(static_cast(r_stl.ptr - buffer_stl), static_cast(r_boost.ptr - buffer_boost)); + BOOST_TEST_CSTR_EQ(buffer_stl, buffer_boost); + } +} + +template +void test() +{ + // Signed 0 + char buffer1_stl[64] {}; + char buffer1_boost[64] {}; + T v1 = static_cast(-0); + auto r1_stl = std::to_chars(buffer1_stl, buffer1_stl + sizeof(buffer1_stl) - 1, v1); + auto r1_boost = boost::charconv::to_chars(buffer1_boost, buffer1_boost + sizeof(buffer1_boost) - 1, v1); + BOOST_TEST_EQ(r1_stl.ptr, buffer1_stl + 1); + BOOST_TEST_EQ(r1_boost.ptr, buffer1_boost + 1); + BOOST_TEST_CSTR_EQ(buffer1_stl, "0"); + BOOST_TEST_CSTR_EQ(buffer1_boost, "0"); + + // Binary + char buffer2_stl[64] {}; + char buffer2_boost[64] {}; + T v2 = static_cast(42); + auto r2_stl = std::to_chars(buffer2_stl, buffer2_stl + sizeof(buffer2_stl) - 1, v2, 2); + auto r2_boost = boost::charconv::to_chars(buffer2_boost, buffer2_boost + sizeof(buffer2_boost) - 1, v2, 2); + BOOST_TEST_EQ(r2_stl.ptr, buffer2_stl + 6); + BOOST_TEST_EQ(r2_boost.ptr, buffer2_boost + 6); + BOOST_TEST_CSTR_EQ(buffer2_stl, "101010"); + BOOST_TEST_CSTR_EQ(buffer2_boost, "101010"); + + // Base 10 + char buffer3_stl[64] {}; + char buffer3_boost[64] {}; + T v3 = static_cast(120); + auto r3_stl = std::to_chars(buffer3_stl, buffer3_stl + sizeof(buffer3_stl) - 1, v3); + auto r3_boost = boost::charconv::to_chars(buffer3_boost, buffer3_boost + sizeof(buffer3_boost) - 1, v3); + BOOST_TEST_EQ(r3_stl.ptr, buffer3_stl + 3); + BOOST_TEST_EQ(r3_boost.ptr, buffer3_boost + 3); + BOOST_TEST_CSTR_EQ(buffer3_stl, "120"); + BOOST_TEST_CSTR_EQ(buffer3_boost, "120"); + + // Hexadecimal + char buffer4_stl[64] {}; + char buffer4_boost[64] {}; + T v4 = static_cast(44); + auto r4_stl = std::to_chars(buffer4_stl, buffer4_stl + sizeof(buffer4_stl) - 1, v4, 16); + auto r4_boost = boost::charconv::to_chars(buffer4_boost, buffer4_boost + sizeof(buffer4_boost) - 1, v4, 16); + BOOST_TEST_EQ(r4_stl.ptr, buffer4_stl + 2); + BOOST_TEST_EQ(r4_boost.ptr, buffer4_boost + 2); + BOOST_TEST_CSTR_EQ(buffer4_stl, "2c"); + BOOST_TEST_CSTR_EQ(buffer4_boost, "2c"); + + // Base 28 + char buffer5_stl[64] {}; + char buffer5_boost[64] {}; + T v5 = static_cast(100); + auto r5_stl = std::to_chars(buffer5_stl, buffer5_stl + sizeof(buffer5_stl) - 1, v5, 28); + auto r5_boost = boost::charconv::to_chars(buffer5_boost, buffer5_boost + sizeof(buffer5_boost) - 1, v5, 28); + BOOST_TEST_EQ(r5_stl.ptr, buffer5_stl + 2); + BOOST_TEST_EQ(r5_boost.ptr, buffer5_boost + 2); + BOOST_TEST_CSTR_EQ(buffer5_stl, "3g"); + BOOST_TEST_CSTR_EQ(buffer5_boost, "3g"); + + // Overflow + char buffer6_stl[1] {}; + char buffer6_boost[1] {}; + T v6 = static_cast(100); + auto r6_stl = std::to_chars(buffer6_stl, buffer6_stl + sizeof(buffer6_stl) - 1, v6); + auto r6_boost = boost::charconv::to_chars(buffer6_boost, buffer6_boost + sizeof(buffer6_boost) - 1, v6); + BOOST_TEST_EQ(r6_stl.ptr, r6_stl.ptr); + BOOST_TEST_EQ(r6_boost.ptr, r6_boost.ptr); +} + +int main() +{ + test(); + test(); + test(); + test(); + test(); + test(); + test(); + test(); + test(); + test(); + test(); + + // Specialized bases + random_tests(); + random_tests(); + random_tests(); + random_tests(); + random_tests(); + + #ifndef _MSC_VER // MSVC does not allow for 8-bit ints in std::uniform_int_distribution + random_tests(); + random_tests(); + #endif + + random_tests(); + random_tests(); + random_tests(); + random_tests(); + random_tests(); + random_tests(); + + // Generic implementation + random_tests(); + + // Stress tests are disabled for CI + #ifdef BOOST_CHARCONV_STRESS_TEST + stress_test(); + #endif + + return boost::report_errors(); +} + +#else + +int main() +{ + return 0; +} + +#endif