Path: blob/master/dep/rapidyaml/include/c4/charconv.hpp
4261 views
#ifndef _C4_CHARCONV_HPP_1#define _C4_CHARCONV_HPP_23/** @file charconv.hpp Lightweight generic type-safe wrappers for4* converting individual values to/from strings.5*6* These are the main functions:7*8* @code{.cpp}9* // Convert the given value, writing into the string.10* // The resulting string will NOT be null-terminated.11* // Return the number of characters needed.12* // This function is safe to call when the string is too small -13* // no writes will occur beyond the string's last character.14* template<class T> size_t c4::to_chars(substr buf, T const& C4_RESTRICT val);15*16*17* // Convert the given value to a string using to_chars(), and18* // return the resulting string, up to and including the last19* // written character.20* template<class T> substr c4::to_chars_sub(substr buf, T const& C4_RESTRICT val);21*22*23* // Read a value from the string, which must be24* // trimmed to the value (ie, no leading/trailing whitespace).25* // return true if the conversion succeeded.26* // There is no check for overflow; the value wraps around in a way similar27* // to the standard C/C++ overflow behavior. For example,28* // from_chars<int8_t>("128", &val) returns true and val will be29* // set tot 0.30* template<class T> bool c4::from_chars(csubstr buf, T * C4_RESTRICT val);31*32*33* // Read the first valid sequence of characters from the string,34* // skipping leading whitespace, and convert it using from_chars().35* // Return the number of characters read for converting.36* template<class T> size_t c4::from_chars_first(csubstr buf, T * C4_RESTRICT val);37* @endcode38*/3940#include "c4/language.hpp"41#include <inttypes.h>42#include <type_traits>43#include <climits>44#include <limits>45#include <utility>4647#include "c4/config.hpp"48#include "c4/substr.hpp"49#include "c4/std/std_fwd.hpp"50#include "c4/memory_util.hpp"51#include "c4/szconv.hpp"5253#ifndef C4CORE_NO_FAST_FLOAT54# if (C4_CPP >= 17)55# if defined(_MSC_VER)56# if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros57# include <charconv>58# define C4CORE_HAVE_STD_TOCHARS 159# define C4CORE_HAVE_STD_FROMCHARS 0 // prefer fast_float with MSVC60# define C4CORE_HAVE_FAST_FLOAT 161# else62# define C4CORE_HAVE_STD_TOCHARS 063# define C4CORE_HAVE_STD_FROMCHARS 064# define C4CORE_HAVE_FAST_FLOAT 165# endif66# else67# if __has_include(<charconv>)68# include <charconv>69# if defined(__cpp_lib_to_chars)70# define C4CORE_HAVE_STD_TOCHARS 171# define C4CORE_HAVE_STD_FROMCHARS 0 // glibc uses fast_float internally72# define C4CORE_HAVE_FAST_FLOAT 173# else74# define C4CORE_HAVE_STD_TOCHARS 075# define C4CORE_HAVE_STD_FROMCHARS 076# define C4CORE_HAVE_FAST_FLOAT 177# endif78# else79# define C4CORE_HAVE_STD_TOCHARS 080# define C4CORE_HAVE_STD_FROMCHARS 081# define C4CORE_HAVE_FAST_FLOAT 182# endif83# endif84# else85# define C4CORE_HAVE_STD_TOCHARS 086# define C4CORE_HAVE_STD_FROMCHARS 087# define C4CORE_HAVE_FAST_FLOAT 188# endif89# if C4CORE_HAVE_FAST_FLOAT90C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wsign-conversion")91C4_SUPPRESS_WARNING_GCC("-Warray-bounds")92# if defined(__GNUC__) && __GNUC__ >= 593C4_SUPPRESS_WARNING_GCC("-Wshift-count-overflow")94# endif95//# include "c4/ext/fast_float.hpp"96#include "fast_float/fast_float.h"97C4_SUPPRESS_WARNING_GCC_POP98# endif99#elif (C4_CPP >= 17)100# define C4CORE_HAVE_FAST_FLOAT 0101# if defined(_MSC_VER)102# if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros103# include <charconv>104# define C4CORE_HAVE_STD_TOCHARS 1105# define C4CORE_HAVE_STD_FROMCHARS 1106# else107# define C4CORE_HAVE_STD_TOCHARS 0108# define C4CORE_HAVE_STD_FROMCHARS 0109# endif110# else111# if __has_include(<charconv>)112# include <charconv>113# if defined(__cpp_lib_to_chars)114# define C4CORE_HAVE_STD_TOCHARS 1115# define C4CORE_HAVE_STD_FROMCHARS 1 // glibc uses fast_float internally116# else117# define C4CORE_HAVE_STD_TOCHARS 0118# define C4CORE_HAVE_STD_FROMCHARS 0119# endif120# else121# define C4CORE_HAVE_STD_TOCHARS 0122# define C4CORE_HAVE_STD_FROMCHARS 0123# endif124# endif125#else126# define C4CORE_HAVE_STD_TOCHARS 0127# define C4CORE_HAVE_STD_FROMCHARS 0128# define C4CORE_HAVE_FAST_FLOAT 0129#endif130131132#if !C4CORE_HAVE_STD_FROMCHARS133#include <cstdio>134#endif135136137#if defined(_MSC_VER)138# pragma warning(push)139# pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe140# if C4_MSVC_VERSION != C4_MSVC_VERSION_2017141# pragma warning(disable: 4800) //'int': forcing value to bool 'true' or 'false' (performance warning)142# endif143#endif144145#if defined(__clang__)146# pragma clang diagnostic push147# pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare"148# pragma clang diagnostic ignored "-Wformat-nonliteral"149# pragma clang diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision150# pragma clang diagnostic ignored "-Wold-style-cast"151#elif defined(__GNUC__)152# pragma GCC diagnostic push153# pragma GCC diagnostic ignored "-Wformat-nonliteral"154# pragma GCC diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision155# pragma GCC diagnostic ignored "-Wuseless-cast"156# pragma GCC diagnostic ignored "-Wold-style-cast"157#endif158159160namespace c4 {161162#if C4CORE_HAVE_STD_TOCHARS163/** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */164typedef enum : std::underlying_type<std::chars_format>::type {165/** print the real number in floating point format (like %f) */166FTOA_FLOAT = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::fixed),167/** print the real number in scientific format (like %e) */168FTOA_SCIENT = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::scientific),169/** print the real number in flexible format (like %g) */170FTOA_FLEX = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::general),171/** print the real number in hexadecimal format (like %a) */172FTOA_HEXA = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::hex),173} RealFormat_e;174#else175/** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */176typedef enum : char {177/** print the real number in floating point format (like %f) */178FTOA_FLOAT = 'f',179/** print the real number in scientific format (like %e) */180FTOA_SCIENT = 'e',181/** print the real number in flexible format (like %g) */182FTOA_FLEX = 'g',183/** print the real number in hexadecimal format (like %a) */184FTOA_HEXA = 'a',185} RealFormat_e;186#endif187188189/** in some platforms, int,unsigned int190* are not any of int8_t...int64_t and191* long,unsigned long are not any of uint8_t...uint64_t */192template<class T>193struct is_fixed_length194{195enum : bool {196/** true if T is one of the fixed length signed types */197value_i = (std::is_integral<T>::value198&& (std::is_same<T, int8_t>::value199|| std::is_same<T, int16_t>::value200|| std::is_same<T, int32_t>::value201|| std::is_same<T, int64_t>::value)),202/** true if T is one of the fixed length unsigned types */203value_u = (std::is_integral<T>::value204&& (std::is_same<T, uint8_t>::value205|| std::is_same<T, uint16_t>::value206|| std::is_same<T, uint32_t>::value207|| std::is_same<T, uint64_t>::value)),208/** true if T is one of the fixed length signed or unsigned types */209value = value_i || value_u210};211};212213214//-----------------------------------------------------------------------------215//-----------------------------------------------------------------------------216//-----------------------------------------------------------------------------217218#ifdef _MSC_VER219# pragma warning(push)220#elif defined(__clang__)221# pragma clang diagnostic push222#elif defined(__GNUC__)223# pragma GCC diagnostic push224# pragma GCC diagnostic ignored "-Wconversion"225# if __GNUC__ >= 6226# pragma GCC diagnostic ignored "-Wnull-dereference"227# endif228#endif229230namespace detail {231232/* python command to get the values below:233def dec(v):234return str(v)235for bits in (8, 16, 32, 64):236imin, imax, umax = (-(1 << (bits - 1))), (1 << (bits - 1)) - 1, (1 << bits) - 1237for vname, v in (("imin", imin), ("imax", imax), ("umax", umax)):238for f in (bin, oct, dec, hex):239print(f"{bits}b: {vname}={v} {f.__name__}: len={len(f(v)):2d}: {v} {f(v)}")240*/241242// do not use the type as the template argument because in some243// platforms long!=int32 and long!=int64. Just use the numbytes244// which is more generic and spares lengthy SFINAE code.245template<size_t num_bytes, bool is_signed> struct charconv_digits_;246template<class T> using charconv_digits = charconv_digits_<sizeof(T), std::is_signed<T>::value>;247248template<> struct charconv_digits_<1u, true> // int8_t249{250enum : size_t {251maxdigits_bin = 1 + 2 + 8, // -128==-0b10000000252maxdigits_oct = 1 + 2 + 3, // -128==-0o200253maxdigits_dec = 1 + 3, // -128254maxdigits_hex = 1 + 2 + 2, // -128==-0x80255maxdigits_bin_nopfx = 8, // -128==-0b10000000256maxdigits_oct_nopfx = 3, // -128==-0o200257maxdigits_dec_nopfx = 3, // -128258maxdigits_hex_nopfx = 2, // -128==-0x80259};260// min values without sign!261static constexpr csubstr min_value_dec() noexcept { return csubstr("128"); }262static constexpr csubstr min_value_hex() noexcept { return csubstr("80"); }263static constexpr csubstr min_value_oct() noexcept { return csubstr("200"); }264static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000"); }265static constexpr csubstr max_value_dec() noexcept { return csubstr("127"); }266static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str[0] <= '1')); }267};268template<> struct charconv_digits_<1u, false> // uint8_t269{270enum : size_t {271maxdigits_bin = 2 + 8, // 255 0b11111111272maxdigits_oct = 2 + 3, // 255 0o377273maxdigits_dec = 3, // 255274maxdigits_hex = 2 + 2, // 255 0xff275maxdigits_bin_nopfx = 8, // 255 0b11111111276maxdigits_oct_nopfx = 3, // 255 0o377277maxdigits_dec_nopfx = 3, // 255278maxdigits_hex_nopfx = 2, // 255 0xff279};280static constexpr csubstr max_value_dec() noexcept { return csubstr("255"); }281static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str[0] <= '3')); }282};283template<> struct charconv_digits_<2u, true> // int16_t284{285enum : size_t {286maxdigits_bin = 1 + 2 + 16, // -32768 -0b1000000000000000287maxdigits_oct = 1 + 2 + 6, // -32768 -0o100000288maxdigits_dec = 1 + 5, // -32768 -32768289maxdigits_hex = 1 + 2 + 4, // -32768 -0x8000290maxdigits_bin_nopfx = 16, // -32768 -0b1000000000000000291maxdigits_oct_nopfx = 6, // -32768 -0o100000292maxdigits_dec_nopfx = 5, // -32768 -32768293maxdigits_hex_nopfx = 4, // -32768 -0x8000294};295// min values without sign!296static constexpr csubstr min_value_dec() noexcept { return csubstr("32768"); }297static constexpr csubstr min_value_hex() noexcept { return csubstr("8000"); }298static constexpr csubstr min_value_oct() noexcept { return csubstr("100000"); }299static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000"); }300static constexpr csubstr max_value_dec() noexcept { return csubstr("32767"); }301static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 6)); }302};303template<> struct charconv_digits_<2u, false> // uint16_t304{305enum : size_t {306maxdigits_bin = 2 + 16, // 65535 0b1111111111111111307maxdigits_oct = 2 + 6, // 65535 0o177777308maxdigits_dec = 6, // 65535 65535309maxdigits_hex = 2 + 4, // 65535 0xffff310maxdigits_bin_nopfx = 16, // 65535 0b1111111111111111311maxdigits_oct_nopfx = 6, // 65535 0o177777312maxdigits_dec_nopfx = 6, // 65535 65535313maxdigits_hex_nopfx = 4, // 65535 0xffff314};315static constexpr csubstr max_value_dec() noexcept { return csubstr("65535"); }316static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 6) || (str.len == 6 && str[0] <= '1')); }317};318template<> struct charconv_digits_<4u, true> // int32_t319{320enum : size_t {321maxdigits_bin = 1 + 2 + 32, // len=35: -2147483648 -0b10000000000000000000000000000000322maxdigits_oct = 1 + 2 + 11, // len=14: -2147483648 -0o20000000000323maxdigits_dec = 1 + 10, // len=11: -2147483648 -2147483648324maxdigits_hex = 1 + 2 + 8, // len=11: -2147483648 -0x80000000325maxdigits_bin_nopfx = 32, // len=35: -2147483648 -0b10000000000000000000000000000000326maxdigits_oct_nopfx = 11, // len=14: -2147483648 -0o20000000000327maxdigits_dec_nopfx = 10, // len=11: -2147483648 -2147483648328maxdigits_hex_nopfx = 8, // len=11: -2147483648 -0x80000000329};330// min values without sign!331static constexpr csubstr min_value_dec() noexcept { return csubstr("2147483648"); }332static constexpr csubstr min_value_hex() noexcept { return csubstr("80000000"); }333static constexpr csubstr min_value_oct() noexcept { return csubstr("20000000000"); }334static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000000000000000000000000000"); }335static constexpr csubstr max_value_dec() noexcept { return csubstr("2147483647"); }336static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str[0] <= '1')); }337};338template<> struct charconv_digits_<4u, false> // uint32_t339{340enum : size_t {341maxdigits_bin = 2 + 32, // len=34: 4294967295 0b11111111111111111111111111111111342maxdigits_oct = 2 + 11, // len=13: 4294967295 0o37777777777343maxdigits_dec = 10, // len=10: 4294967295 4294967295344maxdigits_hex = 2 + 8, // len=10: 4294967295 0xffffffff345maxdigits_bin_nopfx = 32, // len=34: 4294967295 0b11111111111111111111111111111111346maxdigits_oct_nopfx = 11, // len=13: 4294967295 0o37777777777347maxdigits_dec_nopfx = 10, // len=10: 4294967295 4294967295348maxdigits_hex_nopfx = 8, // len=10: 4294967295 0xffffffff349};350static constexpr csubstr max_value_dec() noexcept { return csubstr("4294967295"); }351static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str[0] <= '3')); }352};353template<> struct charconv_digits_<8u, true> // int32_t354{355enum : size_t {356maxdigits_bin = 1 + 2 + 64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000357maxdigits_oct = 1 + 2 + 22, // len=25: -9223372036854775808 -0o1000000000000000000000358maxdigits_dec = 1 + 19, // len=20: -9223372036854775808 -9223372036854775808359maxdigits_hex = 1 + 2 + 16, // len=19: -9223372036854775808 -0x8000000000000000360maxdigits_bin_nopfx = 64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000361maxdigits_oct_nopfx = 22, // len=25: -9223372036854775808 -0o1000000000000000000000362maxdigits_dec_nopfx = 19, // len=20: -9223372036854775808 -9223372036854775808363maxdigits_hex_nopfx = 16, // len=19: -9223372036854775808 -0x8000000000000000364};365static constexpr csubstr min_value_dec() noexcept { return csubstr("9223372036854775808"); }366static constexpr csubstr min_value_hex() noexcept { return csubstr("8000000000000000"); }367static constexpr csubstr min_value_oct() noexcept { return csubstr("1000000000000000000000"); }368static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000000000000000000000000000000000000000000000000000"); }369static constexpr csubstr max_value_dec() noexcept { return csubstr("9223372036854775807"); }370static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 22)); }371};372template<> struct charconv_digits_<8u, false>373{374enum : size_t {375maxdigits_bin = 2 + 64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111376maxdigits_oct = 2 + 22, // len=24: 18446744073709551615 0o1777777777777777777777377maxdigits_dec = 20, // len=20: 18446744073709551615 18446744073709551615378maxdigits_hex = 2 + 16, // len=18: 18446744073709551615 0xffffffffffffffff379maxdigits_bin_nopfx = 64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111380maxdigits_oct_nopfx = 22, // len=24: 18446744073709551615 0o1777777777777777777777381maxdigits_dec_nopfx = 20, // len=20: 18446744073709551615 18446744073709551615382maxdigits_hex_nopfx = 16, // len=18: 18446744073709551615 0xffffffffffffffff383};384static constexpr csubstr max_value_dec() noexcept { return csubstr("18446744073709551615"); }385static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 22) || (str.len == 22 && str[0] <= '1')); }386};387} // namespace detail388389390//-----------------------------------------------------------------------------391//-----------------------------------------------------------------------------392//-----------------------------------------------------------------------------393394// Helper macros, undefined below395#define _c4append(c) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = static_cast<char>(c); } else { ++pos; } }396#define _c4appendhex(i) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = hexchars[i]; } else { ++pos; } }397398/** @name digits_dec return the number of digits required to encode a399* decimal number.400*401* @note At first sight this code may look heavily branchy and402* therefore inefficient. However, measurements revealed this to be403* the fastest among the alternatives.404*405* @see https://github.com/biojppm/c4core/pull/77 */406/** @{ */407408template<class T>409C4_CONSTEXPR14 C4_ALWAYS_INLINE410auto digits_dec(T v) noexcept411-> typename std::enable_if<sizeof(T) == 1u, unsigned>::type412{413C4_STATIC_ASSERT(std::is_integral<T>::value);414C4_ASSERT(v >= 0);415return ((v >= 100) ? 3u : ((v >= 10) ? 2u : 1u));416}417418template<class T>419C4_CONSTEXPR14 C4_ALWAYS_INLINE420auto digits_dec(T v) noexcept421-> typename std::enable_if<sizeof(T) == 2u, unsigned>::type422{423C4_STATIC_ASSERT(std::is_integral<T>::value);424C4_ASSERT(v >= 0);425return ((v >= 10000) ? 5u : (v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u);426}427428template<class T>429C4_CONSTEXPR14 C4_ALWAYS_INLINE430auto digits_dec(T v) noexcept431-> typename std::enable_if<sizeof(T) == 4u, unsigned>::type432{433C4_STATIC_ASSERT(std::is_integral<T>::value);434C4_ASSERT(v >= 0);435return ((v >= 1000000000) ? 10u : (v >= 100000000) ? 9u : (v >= 10000000) ? 8u :436(v >= 1000000) ? 7u : (v >= 100000) ? 6u : (v >= 10000) ? 5u :437(v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u);438}439440template<class T>441C4_CONSTEXPR14 C4_ALWAYS_INLINE442auto digits_dec(T v) noexcept443-> typename std::enable_if<sizeof(T) == 8u, unsigned>::type444{445// thanks @fargies!!!446// https://github.com/biojppm/c4core/pull/77#issuecomment-1063753568447C4_STATIC_ASSERT(std::is_integral<T>::value);448C4_ASSERT(v >= 0);449if(v >= 1000000000) // 10450{451if(v >= 100000000000000) // 15 [15-20] range452{453if(v >= 100000000000000000) // 18 (15 + (20 - 15) / 2)454{455if((typename std::make_unsigned<T>::type)v >= 10000000000000000000u) // 20456return 20u;457else458return (v >= 1000000000000000000) ? 19u : 18u;459}460else if(v >= 10000000000000000) // 17461return 17u;462else463return(v >= 1000000000000000) ? 16u : 15u;464}465else if(v >= 1000000000000) // 13466return (v >= 10000000000000) ? 14u : 13u;467else if(v >= 100000000000) // 12468return 12;469else470return(v >= 10000000000) ? 11u : 10u;471}472else if(v >= 10000) // 5 [5-9] range473{474if(v >= 10000000) // 8475return (v >= 100000000) ? 9u : 8u;476else if(v >= 1000000) // 7477return 7;478else479return (v >= 100000) ? 6u : 5u;480}481else if(v >= 100)482return (v >= 1000) ? 4u : 3u;483else484return (v >= 10) ? 2u : 1u;485}486487/** @} */488489490template<class T>491C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_hex(T v) noexcept492{493C4_STATIC_ASSERT(std::is_integral<T>::value);494C4_ASSERT(v >= 0);495return v ? 1u + (msb((typename std::make_unsigned<T>::type)v) >> 2u) : 1u;496}497498template<class T>499C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_bin(T v) noexcept500{501C4_STATIC_ASSERT(std::is_integral<T>::value);502C4_ASSERT(v >= 0);503return v ? 1u + msb((typename std::make_unsigned<T>::type)v) : 1u;504}505506template<class T>507C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_oct(T v_) noexcept508{509// TODO: is there a better way?510C4_STATIC_ASSERT(std::is_integral<T>::value);511C4_ASSERT(v_ >= 0);512using U = typename513std::conditional<sizeof(T) <= sizeof(unsigned),514unsigned,515typename std::make_unsigned<T>::type>::type;516U v = (U) v_; // safe because we require v_ >= 0517unsigned __n = 1;518const unsigned __b2 = 64u;519const unsigned __b3 = __b2 * 8u;520const unsigned long __b4 = __b3 * 8u;521while(true)522{523if(v < 8u)524return __n;525if(v < __b2)526return __n + 1;527if(v < __b3)528return __n + 2;529if(v < __b4)530return __n + 3;531v /= (U) __b4;532__n += 4;533}534}535536537//-----------------------------------------------------------------------------538//-----------------------------------------------------------------------------539//-----------------------------------------------------------------------------540541namespace detail {542C4_INLINE_CONSTEXPR const char hexchars[] = "0123456789abcdef";543C4_INLINE_CONSTEXPR const char digits0099[] =544"0001020304050607080910111213141516171819"545"2021222324252627282930313233343536373839"546"4041424344454647484950515253545556575859"547"6061626364656667686970717273747576777879"548"8081828384858687888990919293949596979899";549} // namespace detail550551C4_SUPPRESS_WARNING_GCC_PUSH552C4_SUPPRESS_WARNING_GCC("-Warray-bounds") // gcc has false positives here553#if (defined(__GNUC__) && (__GNUC__ >= 7))554C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has false positives here555#endif556557template<class T>558C4_HOT C4_ALWAYS_INLINE559void write_dec_unchecked(substr buf, T v, unsigned digits_v) noexcept560{561C4_STATIC_ASSERT(std::is_integral<T>::value);562C4_ASSERT(v >= 0);563C4_ASSERT(buf.len >= digits_v);564C4_XASSERT(digits_v == digits_dec(v));565// in bm_xtoa: checkoncelog_singlediv_write2566while(v >= T(100))567{568T quo = v;569quo /= T(100);570const auto num = (v - quo * T(100)) << 1u;571v = quo;572buf.str[--digits_v] = detail::digits0099[num + 1];573buf.str[--digits_v] = detail::digits0099[num];574}575if(v >= T(10))576{577C4_ASSERT(digits_v == 2);578const auto num = v << 1u;579buf.str[1] = detail::digits0099[num + 1];580buf.str[0] = detail::digits0099[num];581}582else583{584C4_ASSERT(digits_v == 1);585buf.str[0] = (char)('0' + v);586}587}588589590template<class T>591C4_HOT C4_ALWAYS_INLINE592void write_hex_unchecked(substr buf, T v, unsigned digits_v) noexcept593{594C4_STATIC_ASSERT(std::is_integral<T>::value);595C4_ASSERT(v >= 0);596C4_ASSERT(buf.len >= digits_v);597C4_XASSERT(digits_v == digits_hex(v));598do {599buf.str[--digits_v] = detail::hexchars[v & T(15)];600v >>= 4;601} while(v);602C4_ASSERT(digits_v == 0);603}604605606template<class T>607C4_HOT C4_ALWAYS_INLINE608void write_oct_unchecked(substr buf, T v, unsigned digits_v) noexcept609{610C4_STATIC_ASSERT(std::is_integral<T>::value);611C4_ASSERT(v >= 0);612C4_ASSERT(buf.len >= digits_v);613C4_XASSERT(digits_v == digits_oct(v));614do {615buf.str[--digits_v] = (char)('0' + (v & T(7)));616v >>= 3;617} while(v);618C4_ASSERT(digits_v == 0);619}620621622template<class T>623C4_HOT C4_ALWAYS_INLINE624void write_bin_unchecked(substr buf, T v, unsigned digits_v) noexcept625{626C4_STATIC_ASSERT(std::is_integral<T>::value);627C4_ASSERT(v >= 0);628C4_ASSERT(buf.len >= digits_v);629C4_XASSERT(digits_v == digits_bin(v));630do {631buf.str[--digits_v] = (char)('0' + (v & T(1)));632v >>= 1;633} while(v);634C4_ASSERT(digits_v == 0);635}636637638/** write an integer to a string in decimal format. This is the639* lowest level (and the fastest) function to do this task.640* @note does not accept negative numbers641* @note the resulting string is NOT zero-terminated.642* @note it is ok to call this with an empty or too-small buffer;643* no writes will occur, and the required size will be returned644* @return the number of characters required for the buffer. */645template<class T>646C4_ALWAYS_INLINE size_t write_dec(substr buf, T v) noexcept647{648C4_STATIC_ASSERT(std::is_integral<T>::value);649C4_ASSERT(v >= 0);650unsigned digits = digits_dec(v);651if(C4_LIKELY(buf.len >= digits))652write_dec_unchecked(buf, v, digits);653return digits;654}655656/** write an integer to a string in hexadecimal format. This is the657* lowest level (and the fastest) function to do this task.658* @note does not accept negative numbers659* @note does not prefix with 0x660* @note the resulting string is NOT zero-terminated.661* @note it is ok to call this with an empty or too-small buffer;662* no writes will occur, and the required size will be returned663* @return the number of characters required for the buffer. */664template<class T>665C4_ALWAYS_INLINE size_t write_hex(substr buf, T v) noexcept666{667C4_STATIC_ASSERT(std::is_integral<T>::value);668C4_ASSERT(v >= 0);669unsigned digits = digits_hex(v);670if(C4_LIKELY(buf.len >= digits))671write_hex_unchecked(buf, v, digits);672return digits;673}674675/** write an integer to a string in octal format. This is the676* lowest level (and the fastest) function to do this task.677* @note does not accept negative numbers678* @note does not prefix with 0o679* @note the resulting string is NOT zero-terminated.680* @note it is ok to call this with an empty or too-small buffer;681* no writes will occur, and the required size will be returned682* @return the number of characters required for the buffer. */683template<class T>684C4_ALWAYS_INLINE size_t write_oct(substr buf, T v) noexcept685{686C4_STATIC_ASSERT(std::is_integral<T>::value);687C4_ASSERT(v >= 0);688unsigned digits = digits_oct(v);689if(C4_LIKELY(buf.len >= digits))690write_oct_unchecked(buf, v, digits);691return digits;692}693694/** write an integer to a string in binary format. This is the695* lowest level (and the fastest) function to do this task.696* @note does not accept negative numbers697* @note does not prefix with 0b698* @note the resulting string is NOT zero-terminated.699* @note it is ok to call this with an empty or too-small buffer;700* no writes will occur, and the required size will be returned701* @return the number of characters required for the buffer. */702template<class T>703C4_ALWAYS_INLINE size_t write_bin(substr buf, T v) noexcept704{705C4_STATIC_ASSERT(std::is_integral<T>::value);706C4_ASSERT(v >= 0);707unsigned digits = digits_bin(v);708C4_ASSERT(digits > 0);709if(C4_LIKELY(buf.len >= digits))710write_bin_unchecked(buf, v, digits);711return digits;712}713714715namespace detail {716template<class U> using NumberWriter = size_t (*)(substr, U);717template<class T, NumberWriter<T> writer>718size_t write_num_digits(substr buf, T v, size_t num_digits) noexcept719{720C4_STATIC_ASSERT(std::is_integral<T>::value);721size_t ret = writer(buf, v);722if(ret >= num_digits)723return ret;724else if(ret >= buf.len || num_digits > buf.len)725return num_digits;726C4_ASSERT(num_digits >= ret);727size_t delta = static_cast<size_t>(num_digits - ret);728memmove(buf.str + delta, buf.str, ret);729memset(buf.str, '0', delta);730return num_digits;731}732} // namespace detail733734735/** same as c4::write_dec(), but pad with zeroes on the left736* such that the resulting string is @p num_digits wide.737* If the given number is requires more than num_digits, then the number prevails. */738template<class T>739C4_ALWAYS_INLINE size_t write_dec(substr buf, T val, size_t num_digits) noexcept740{741return detail::write_num_digits<T, &write_dec<T>>(buf, val, num_digits);742}743744/** same as c4::write_hex(), but pad with zeroes on the left745* such that the resulting string is @p num_digits wide.746* If the given number is requires more than num_digits, then the number prevails. */747template<class T>748C4_ALWAYS_INLINE size_t write_hex(substr buf, T val, size_t num_digits) noexcept749{750return detail::write_num_digits<T, &write_hex<T>>(buf, val, num_digits);751}752753/** same as c4::write_bin(), but pad with zeroes on the left754* such that the resulting string is @p num_digits wide.755* If the given number is requires more than num_digits, then the number prevails. */756template<class T>757C4_ALWAYS_INLINE size_t write_bin(substr buf, T val, size_t num_digits) noexcept758{759return detail::write_num_digits<T, &write_bin<T>>(buf, val, num_digits);760}761762/** same as c4::write_oct(), but pad with zeroes on the left763* such that the resulting string is @p num_digits wide.764* If the given number is requires more than num_digits, then the number prevails. */765template<class T>766C4_ALWAYS_INLINE size_t write_oct(substr buf, T val, size_t num_digits) noexcept767{768return detail::write_num_digits<T, &write_oct<T>>(buf, val, num_digits);769}770771C4_SUPPRESS_WARNING_GCC_POP772773774//-----------------------------------------------------------------------------775//-----------------------------------------------------------------------------776//-----------------------------------------------------------------------------777778779C4_SUPPRESS_WARNING_MSVC_PUSH780C4_SUPPRESS_WARNING_MSVC(4365) // '=': conversion from 'int' to 'I', signed/unsigned mismatch781782/** read a decimal integer from a string. This is the783* lowest level (and the fastest) function to do this task.784* @note does not accept negative numbers785* @note The string must be trimmed. Whitespace is not accepted.786* @note the string must not be empty787* @note there is no check for overflow; the value wraps around788* in a way similar to the standard C/C++ overflow behavior.789* For example, `read_dec<int8_t>("128", &val)` returns true790* and val will be set to 0 because 127 is the max i8 value.791* @see overflows<T>() to find out if a number string overflows a type range792* @return true if the conversion was successful (no overflow check) */793template<class I>794C4_ALWAYS_INLINE bool read_dec(csubstr s, I *C4_RESTRICT v) noexcept795{796C4_STATIC_ASSERT(std::is_integral<I>::value);797C4_ASSERT(!s.empty());798*v = 0;799for(char c : s)800{801if(C4_UNLIKELY(c < '0' || c > '9'))802return false;803*v = (*v) * I(10) + (I(c) - I('0'));804}805return true;806}807808/** read an hexadecimal integer from a string. This is the809* lowest level (and the fastest) function to do this task.810* @note does not accept negative numbers811* @note does not accept leading 0x or 0X812* @note the string must not be empty813* @note the string must be trimmed. Whitespace is not accepted.814* @note there is no check for overflow; the value wraps around815* in a way similar to the standard C/C++ overflow behavior.816* For example, `read_hex<int8_t>("80", &val)` returns true817* and val will be set to 0 because 7f is the max i8 value.818* @see overflows<T>() to find out if a number string overflows a type range819* @return true if the conversion was successful (no overflow check) */820template<class I>821C4_ALWAYS_INLINE bool read_hex(csubstr s, I *C4_RESTRICT v) noexcept822{823C4_STATIC_ASSERT(std::is_integral<I>::value);824C4_ASSERT(!s.empty());825*v = 0;826for(char c : s)827{828I cv;829if(c >= '0' && c <= '9')830cv = I(c) - I('0');831else if(c >= 'a' && c <= 'f')832cv = I(10) + (I(c) - I('a'));833else if(c >= 'A' && c <= 'F')834cv = I(10) + (I(c) - I('A'));835else836return false;837*v = (*v) * I(16) + cv;838}839return true;840}841842/** read a binary integer from a string. This is the843* lowest level (and the fastest) function to do this task.844* @note does not accept negative numbers845* @note does not accept leading 0b or 0B846* @note the string must not be empty847* @note the string must be trimmed. Whitespace is not accepted.848* @note there is no check for overflow; the value wraps around849* in a way similar to the standard C/C++ overflow behavior.850* For example, `read_bin<int8_t>("10000000", &val)` returns true851* and val will be set to 0 because 1111111 is the max i8 value.852* @see overflows<T>() to find out if a number string overflows a type range853* @return true if the conversion was successful (no overflow check) */854template<class I>855C4_ALWAYS_INLINE bool read_bin(csubstr s, I *C4_RESTRICT v) noexcept856{857C4_STATIC_ASSERT(std::is_integral<I>::value);858C4_ASSERT(!s.empty());859*v = 0;860for(char c : s)861{862*v <<= 1;863if(c == '1')864*v |= 1;865else if(c != '0')866return false;867}868return true;869}870871/** read an octal integer from a string. This is the872* lowest level (and the fastest) function to do this task.873* @note does not accept negative numbers874* @note does not accept leading 0o or 0O875* @note the string must not be empty876* @note the string must be trimmed. Whitespace is not accepted.877* @note there is no check for overflow; the value wraps around878* in a way similar to the standard C/C++ overflow behavior.879* For example, `read_oct<int8_t>("200", &val)` returns true880* and val will be set to 0 because 177 is the max i8 value.881* @see overflows<T>() to find out if a number string overflows a type range882* @return true if the conversion was successful (no overflow check) */883template<class I>884C4_ALWAYS_INLINE bool read_oct(csubstr s, I *C4_RESTRICT v) noexcept885{886C4_STATIC_ASSERT(std::is_integral<I>::value);887C4_ASSERT(!s.empty());888*v = 0;889for(char c : s)890{891if(C4_UNLIKELY(c < '0' || c > '7'))892return false;893*v = (*v) * I(8) + (I(c) - I('0'));894}895return true;896}897898C4_SUPPRESS_WARNING_MSVC_POP899900901//-----------------------------------------------------------------------------902//-----------------------------------------------------------------------------903//-----------------------------------------------------------------------------904905C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wswitch-default")906907namespace detail {908inline size_t _itoa2buf(substr buf, size_t pos, csubstr val) noexcept909{910C4_ASSERT(pos + val.len <= buf.len);911memcpy(buf.str + pos, val.str, val.len);912return pos + val.len;913}914inline size_t _itoa2bufwithdigits(substr buf, size_t pos, size_t num_digits, csubstr val) noexcept915{916num_digits = num_digits > val.len ? num_digits - val.len : 0;917C4_ASSERT(num_digits + val.len <= buf.len);918for(size_t i = 0; i < num_digits; ++i)919_c4append('0');920return detail::_itoa2buf(buf, pos, val);921}922template<class I>923C4_NO_INLINE size_t _itoadec2buf(substr buf) noexcept924{925using digits_type = detail::charconv_digits<I>;926if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec))927return digits_type::maxdigits_dec;928buf.str[0] = '-';929return detail::_itoa2buf(buf, 1, digits_type::min_value_dec());930}931template<class I>932C4_NO_INLINE size_t _itoa2buf(substr buf, I radix) noexcept933{934using digits_type = detail::charconv_digits<I>;935size_t pos = 0;936if(C4_LIKELY(buf.len > 0))937buf.str[pos++] = '-';938switch(radix)939{940case I(10):941if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec))942return digits_type::maxdigits_dec;943pos =_itoa2buf(buf, pos, digits_type::min_value_dec());944break;945case I(16):946if(C4_UNLIKELY(buf.len < digits_type::maxdigits_hex))947return digits_type::maxdigits_hex;948buf.str[pos++] = '0';949buf.str[pos++] = 'x';950pos = _itoa2buf(buf, pos, digits_type::min_value_hex());951break;952case I( 2):953if(C4_UNLIKELY(buf.len < digits_type::maxdigits_bin))954return digits_type::maxdigits_bin;955buf.str[pos++] = '0';956buf.str[pos++] = 'b';957pos = _itoa2buf(buf, pos, digits_type::min_value_bin());958break;959case I( 8):960if(C4_UNLIKELY(buf.len < digits_type::maxdigits_oct))961return digits_type::maxdigits_oct;962buf.str[pos++] = '0';963buf.str[pos++] = 'o';964pos = _itoa2buf(buf, pos, digits_type::min_value_oct());965break;966}967return pos;968}969template<class I>970C4_NO_INLINE size_t _itoa2buf(substr buf, I radix, size_t num_digits) noexcept971{972using digits_type = detail::charconv_digits<I>;973size_t pos = 0;974size_t needed_digits = 0;975if(C4_LIKELY(buf.len > 0))976buf.str[pos++] = '-';977switch(radix)978{979case I(10):980// add 1 to account for -981needed_digits = num_digits+1 > digits_type::maxdigits_dec ? num_digits+1 : digits_type::maxdigits_dec;982if(C4_UNLIKELY(buf.len < needed_digits))983return needed_digits;984pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_dec());985break;986case I(16):987// add 3 to account for -0x988needed_digits = num_digits+3 > digits_type::maxdigits_hex ? num_digits+3 : digits_type::maxdigits_hex;989if(C4_UNLIKELY(buf.len < needed_digits))990return needed_digits;991buf.str[pos++] = '0';992buf.str[pos++] = 'x';993pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_hex());994break;995case I(2):996// add 3 to account for -0b997needed_digits = num_digits+3 > digits_type::maxdigits_bin ? num_digits+3 : digits_type::maxdigits_bin;998if(C4_UNLIKELY(buf.len < needed_digits))999return needed_digits;1000C4_ASSERT(buf.len >= digits_type::maxdigits_bin);1001buf.str[pos++] = '0';1002buf.str[pos++] = 'b';1003pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_bin());1004break;1005case I(8):1006// add 3 to account for -0o1007needed_digits = num_digits+3 > digits_type::maxdigits_oct ? num_digits+3 : digits_type::maxdigits_oct;1008if(C4_UNLIKELY(buf.len < needed_digits))1009return needed_digits;1010C4_ASSERT(buf.len >= digits_type::maxdigits_oct);1011buf.str[pos++] = '0';1012buf.str[pos++] = 'o';1013pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_oct());1014break;1015}1016return pos;1017}1018} // namespace detail101910201021/** convert an integral signed decimal to a string.1022* @note the resulting string is NOT zero-terminated.1023* @note it is ok to call this with an empty or too-small buffer;1024* no writes will occur, and the needed size will be returned1025* @return the number of characters required for the buffer. */1026template<class T>1027C4_ALWAYS_INLINE size_t itoa(substr buf, T v) noexcept1028{1029C4_STATIC_ASSERT(std::is_signed<T>::value);1030if(v >= T(0))1031{1032// write_dec() checks the buffer size, so no need to check here1033return write_dec(buf, v);1034}1035// when T is the min value (eg i8: -128), negating it1036// will overflow, so treat the min as a special case1037else if(C4_LIKELY(v != std::numeric_limits<T>::min()))1038{1039v = -v;1040unsigned digits = digits_dec(v);1041if(C4_LIKELY(buf.len >= digits + 1u))1042{1043buf.str[0] = '-';1044write_dec_unchecked(buf.sub(1), v, digits);1045}1046return digits + 1u;1047}1048return detail::_itoadec2buf<T>(buf);1049}10501051/** convert an integral signed integer to a string, using a specific1052* radix. The radix must be 2, 8, 10 or 16.1053*1054* @note the resulting string is NOT zero-terminated.1055* @note it is ok to call this with an empty or too-small buffer;1056* no writes will occur, and the needed size will be returned1057* @return the number of characters required for the buffer. */1058template<class T>1059C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix) noexcept1060{1061C4_STATIC_ASSERT(std::is_signed<T>::value);1062C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16);1063C4_SUPPRESS_WARNING_GCC_PUSH1064#if (defined(__GNUC__) && (__GNUC__ >= 7))1065C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has a false positive here1066#endif1067// when T is the min value (eg i8: -128), negating it1068// will overflow, so treat the min as a special case1069if(C4_LIKELY(v != std::numeric_limits<T>::min()))1070{1071unsigned pos = 0;1072if(v < 0)1073{1074v = -v;1075if(C4_LIKELY(buf.len > 0))1076buf.str[pos] = '-';1077++pos;1078}1079unsigned digits = 0;1080switch(radix)1081{1082case T(10):1083digits = digits_dec(v);1084if(C4_LIKELY(buf.len >= pos + digits))1085write_dec_unchecked(buf.sub(pos), v, digits);1086break;1087case T(16):1088digits = digits_hex(v);1089if(C4_LIKELY(buf.len >= pos + 2u + digits))1090{1091buf.str[pos + 0] = '0';1092buf.str[pos + 1] = 'x';1093write_hex_unchecked(buf.sub(pos + 2), v, digits);1094}1095digits += 2u;1096break;1097case T(2):1098digits = digits_bin(v);1099if(C4_LIKELY(buf.len >= pos + 2u + digits))1100{1101buf.str[pos + 0] = '0';1102buf.str[pos + 1] = 'b';1103write_bin_unchecked(buf.sub(pos + 2), v, digits);1104}1105digits += 2u;1106break;1107case T(8):1108digits = digits_oct(v);1109if(C4_LIKELY(buf.len >= pos + 2u + digits))1110{1111buf.str[pos + 0] = '0';1112buf.str[pos + 1] = 'o';1113write_oct_unchecked(buf.sub(pos + 2), v, digits);1114}1115digits += 2u;1116break;1117}1118return pos + digits;1119}1120C4_SUPPRESS_WARNING_GCC_POP1121// when T is the min value (eg i8: -128), negating it1122// will overflow1123return detail::_itoa2buf<T>(buf, radix);1124}112511261127/** same as c4::itoa(), but pad with zeroes on the left such that the1128* resulting string is @p num_digits wide, not accounting for radix1129* prefix (0x,0o,0b). The @p radix must be 2, 8, 10 or 16.1130*1131* @note the resulting string is NOT zero-terminated.1132* @note it is ok to call this with an empty or too-small buffer;1133* no writes will occur, and the needed size will be returned1134* @return the number of characters required for the buffer. */1135template<class T>1136C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix, size_t num_digits) noexcept1137{1138C4_STATIC_ASSERT(std::is_signed<T>::value);1139C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16);1140C4_SUPPRESS_WARNING_GCC_PUSH1141#if (defined(__GNUC__) && (__GNUC__ >= 7))1142C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has a false positive here1143#endif1144// when T is the min value (eg i8: -128), negating it1145// will overflow, so treat the min as a special case1146if(C4_LIKELY(v != std::numeric_limits<T>::min()))1147{1148unsigned pos = 0;1149if(v < 0)1150{1151v = -v;1152if(C4_LIKELY(buf.len > 0))1153buf.str[pos] = '-';1154++pos;1155}1156unsigned total_digits = 0;1157switch(radix)1158{1159case T(10):1160total_digits = digits_dec(v);1161total_digits = pos + (unsigned)(num_digits > total_digits ? num_digits : total_digits);1162if(C4_LIKELY(buf.len >= total_digits))1163write_dec(buf.sub(pos), v, num_digits);1164break;1165case T(16):1166total_digits = digits_hex(v);1167total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);1168if(C4_LIKELY(buf.len >= total_digits))1169{1170buf.str[pos + 0] = '0';1171buf.str[pos + 1] = 'x';1172write_hex(buf.sub(pos + 2), v, num_digits);1173}1174break;1175case T(2):1176total_digits = digits_bin(v);1177total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);1178if(C4_LIKELY(buf.len >= total_digits))1179{1180buf.str[pos + 0] = '0';1181buf.str[pos + 1] = 'b';1182write_bin(buf.sub(pos + 2), v, num_digits);1183}1184break;1185case T(8):1186total_digits = digits_oct(v);1187total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);1188if(C4_LIKELY(buf.len >= total_digits))1189{1190buf.str[pos + 0] = '0';1191buf.str[pos + 1] = 'o';1192write_oct(buf.sub(pos + 2), v, num_digits);1193}1194break;1195}1196return total_digits;1197}1198C4_SUPPRESS_WARNING_GCC_POP1199// when T is the min value (eg i8: -128), negating it1200// will overflow1201return detail::_itoa2buf<T>(buf, radix, num_digits);1202}120312041205//-----------------------------------------------------------------------------1206//-----------------------------------------------------------------------------1207//-----------------------------------------------------------------------------12081209/** convert an integral unsigned decimal to a string.1210*1211* @note the resulting string is NOT zero-terminated.1212* @note it is ok to call this with an empty or too-small buffer;1213* no writes will occur, and the needed size will be returned1214* @return the number of characters required for the buffer. */1215template<class T>1216C4_ALWAYS_INLINE size_t utoa(substr buf, T v) noexcept1217{1218C4_STATIC_ASSERT(std::is_unsigned<T>::value);1219// write_dec() does the buffer length check, so no need to check here1220return write_dec(buf, v);1221}12221223/** convert an integral unsigned integer to a string, using a specific1224* radix. The radix must be 2, 8, 10 or 16.1225*1226* @note the resulting string is NOT zero-terminated.1227* @note it is ok to call this with an empty or too-small buffer;1228* no writes will occur, and the needed size will be returned1229* @return the number of characters required for the buffer. */1230template<class T>1231C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix) noexcept1232{1233C4_STATIC_ASSERT(std::is_unsigned<T>::value);1234C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8);1235unsigned digits = 0;1236switch(radix)1237{1238case T(10):1239digits = digits_dec(v);1240if(C4_LIKELY(buf.len >= digits))1241write_dec_unchecked(buf, v, digits);1242break;1243case T(16):1244digits = digits_hex(v);1245if(C4_LIKELY(buf.len >= digits+2u))1246{1247buf.str[0] = '0';1248buf.str[1] = 'x';1249write_hex_unchecked(buf.sub(2), v, digits);1250}1251digits += 2u;1252break;1253case T(2):1254digits = digits_bin(v);1255if(C4_LIKELY(buf.len >= digits+2u))1256{1257buf.str[0] = '0';1258buf.str[1] = 'b';1259write_bin_unchecked(buf.sub(2), v, digits);1260}1261digits += 2u;1262break;1263case T(8):1264digits = digits_oct(v);1265if(C4_LIKELY(buf.len >= digits+2u))1266{1267buf.str[0] = '0';1268buf.str[1] = 'o';1269write_oct_unchecked(buf.sub(2), v, digits);1270}1271digits += 2u;1272break;1273}1274return digits;1275}12761277/** same as c4::utoa(), but pad with zeroes on the left such that the1278* resulting string is @p num_digits wide. The @p radix must be 2,1279* 8, 10 or 16.1280*1281* @note the resulting string is NOT zero-terminated.1282* @note it is ok to call this with an empty or too-small buffer;1283* no writes will occur, and the needed size will be returned1284* @return the number of characters required for the buffer. */1285template<class T>1286C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix, size_t num_digits) noexcept1287{1288C4_STATIC_ASSERT(std::is_unsigned<T>::value);1289C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8);1290unsigned total_digits = 0;1291switch(radix)1292{1293case T(10):1294total_digits = digits_dec(v);1295total_digits = (unsigned)(num_digits > total_digits ? num_digits : total_digits);1296if(C4_LIKELY(buf.len >= total_digits))1297write_dec(buf, v, num_digits);1298break;1299case T(16):1300total_digits = digits_hex(v);1301total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);1302if(C4_LIKELY(buf.len >= total_digits))1303{1304buf.str[0] = '0';1305buf.str[1] = 'x';1306write_hex(buf.sub(2), v, num_digits);1307}1308break;1309case T(2):1310total_digits = digits_bin(v);1311total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);1312if(C4_LIKELY(buf.len >= total_digits))1313{1314buf.str[0] = '0';1315buf.str[1] = 'b';1316write_bin(buf.sub(2), v, num_digits);1317}1318break;1319case T(8):1320total_digits = digits_oct(v);1321total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);1322if(C4_LIKELY(buf.len >= total_digits))1323{1324buf.str[0] = '0';1325buf.str[1] = 'o';1326write_oct(buf.sub(2), v, num_digits);1327}1328break;1329}1330return total_digits;1331}1332C4_SUPPRESS_WARNING_GCC_POP133313341335//-----------------------------------------------------------------------------1336//-----------------------------------------------------------------------------1337//-----------------------------------------------------------------------------13381339/** Convert a trimmed string to a signed integral value. The input1340* string can be formatted as decimal, binary (prefix 0b or 0B), octal1341* (prefix 0o or 0O) or hexadecimal (prefix 0x or 0X). Strings with1342* leading zeroes are considered as decimal and not octal (unlike the1343* C/C++ convention). Every character in the input string is read for1344* the conversion; the input string must not contain any leading or1345* trailing whitespace.1346*1347* @return true if the conversion was successful.1348*1349* @note overflow is not detected: the return status is true even if1350* the conversion would return a value outside of the type's range, in1351* which case the result will wrap around the type's range.1352* This is similar to native behavior.1353*1354* @note a positive sign is not accepted. ie, the string must not1355* start with '+'1356*1357* @see atoi_first() if the string is not trimmed to the value to read. */1358template<class T>1359C4_ALWAYS_INLINE bool atoi(csubstr str, T * C4_RESTRICT v) noexcept1360{1361C4_STATIC_ASSERT(std::is_integral<T>::value);1362C4_STATIC_ASSERT(std::is_signed<T>::value);13631364if(C4_UNLIKELY(str.len == 0))1365return false;13661367C4_ASSERT(str.str[0] != '+');13681369T sign = 1;1370size_t start = 0;1371if(str.str[0] == '-')1372{1373if(C4_UNLIKELY(str.len == ++start))1374return false;1375sign = -1;1376}13771378bool parsed_ok = true;1379if(str.str[start] != '0') // this should be the common case, so put it first1380{1381parsed_ok = read_dec(str.sub(start), v);1382}1383else if(str.len > start + 1)1384{1385// starts with 0: is it 0x, 0o, 0b?1386const char pfx = str.str[start + 1];1387if(pfx == 'x' || pfx == 'X')1388parsed_ok = str.len > start + 2 && read_hex(str.sub(start + 2), v);1389else if(pfx == 'b' || pfx == 'B')1390parsed_ok = str.len > start + 2 && read_bin(str.sub(start + 2), v);1391else if(pfx == 'o' || pfx == 'O')1392parsed_ok = str.len > start + 2 && read_oct(str.sub(start + 2), v);1393else1394parsed_ok = read_dec(str.sub(start + 1), v);1395}1396else1397{1398parsed_ok = read_dec(str.sub(start), v);1399}1400if(C4_LIKELY(parsed_ok))1401*v *= sign;1402return parsed_ok;1403}140414051406/** Select the next range of characters in the string that can be parsed1407* as a signed integral value, and convert it using atoi(). Leading1408* whitespace (space, newline, tabs) is skipped.1409* @return the number of characters read for conversion, or csubstr::npos if the conversion failed1410* @see atoi() if the string is already trimmed to the value to read.1411* @see csubstr::first_int_span() */1412template<class T>1413C4_ALWAYS_INLINE size_t atoi_first(csubstr str, T * C4_RESTRICT v)1414{1415csubstr trimmed = str.first_int_span();1416if(trimmed.len == 0)1417return csubstr::npos;1418if(atoi(trimmed, v))1419return static_cast<size_t>(trimmed.end() - str.begin());1420return csubstr::npos;1421}142214231424//-----------------------------------------------------------------------------14251426/** Convert a trimmed string to an unsigned integral value. The string can be1427* formatted as decimal, binary (prefix 0b or 0B), octal (prefix 0o or 0O)1428* or hexadecimal (prefix 0x or 0X). Every character in the input string is read1429* for the conversion; it must not contain any leading or trailing whitespace.1430*1431* @return true if the conversion was successful.1432*1433* @note overflow is not detected: the return status is true even if1434* the conversion would return a value outside of the type's range, in1435* which case the result will wrap around the type's range.1436*1437* @note If the string has a minus character, the return status1438* will be false.1439*1440* @see atou_first() if the string is not trimmed to the value to read. */1441template<class T>1442bool atou(csubstr str, T * C4_RESTRICT v) noexcept1443{1444C4_STATIC_ASSERT(std::is_integral<T>::value);14451446if(C4_UNLIKELY(str.len == 0 || str.front() == '-'))1447return false;14481449bool parsed_ok = true;1450if(str.str[0] != '0')1451{1452parsed_ok = read_dec(str, v);1453}1454else1455{1456if(str.len > 1)1457{1458const char pfx = str.str[1];1459if(pfx == 'x' || pfx == 'X')1460parsed_ok = str.len > 2 && read_hex(str.sub(2), v);1461else if(pfx == 'b' || pfx == 'B')1462parsed_ok = str.len > 2 && read_bin(str.sub(2), v);1463else if(pfx == 'o' || pfx == 'O')1464parsed_ok = str.len > 2 && read_oct(str.sub(2), v);1465else1466parsed_ok = read_dec(str, v);1467}1468else1469{1470*v = 0; // we know the first character is 01471}1472}1473return parsed_ok;1474}147514761477/** Select the next range of characters in the string that can be parsed1478* as an unsigned integral value, and convert it using atou(). Leading1479* whitespace (space, newline, tabs) is skipped.1480* @return the number of characters read for conversion, or csubstr::npos if the conversion faileds1481* @see atou() if the string is already trimmed to the value to read.1482* @see csubstr::first_uint_span() */1483template<class T>1484C4_ALWAYS_INLINE size_t atou_first(csubstr str, T *v)1485{1486csubstr trimmed = str.first_uint_span();1487if(trimmed.len == 0)1488return csubstr::npos;1489if(atou(trimmed, v))1490return static_cast<size_t>(trimmed.end() - str.begin());1491return csubstr::npos;1492}149314941495#ifdef _MSC_VER1496# pragma warning(pop)1497#elif defined(__clang__)1498# pragma clang diagnostic pop1499#elif defined(__GNUC__)1500# pragma GCC diagnostic pop1501#endif150215031504//-----------------------------------------------------------------------------1505//-----------------------------------------------------------------------------1506//-----------------------------------------------------------------------------1507namespace detail {1508inline bool check_overflow(csubstr str, csubstr limit) noexcept1509{1510if(str.len == limit.len)1511{1512for(size_t i = 0; i < limit.len; ++i)1513{1514if(str[i] < limit[i])1515return false;1516else if(str[i] > limit[i])1517return true;1518}1519return false;1520}1521else1522return str.len > limit.len;1523}1524} // namespace detail152515261527/** Test if the following string would overflow when converted to associated1528* types.1529* @return true if number will overflow, false if it fits (or doesn't parse)1530*/1531template<class T>1532auto overflows(csubstr str) noexcept1533-> typename std::enable_if<std::is_unsigned<T>::value, bool>::type1534{1535C4_STATIC_ASSERT(std::is_integral<T>::value);15361537if(C4_UNLIKELY(str.len == 0))1538{1539return false;1540}1541else if(str.str[0] == '0')1542{1543if (str.len == 1)1544return false;1545switch (str.str[1])1546{1547case 'x':1548case 'X':1549{1550size_t fno = str.first_not_of('0', 2);1551if (fno == csubstr::npos)1552return false;1553return !(str.len <= fno + (sizeof(T) * 2));1554}1555case 'b':1556case 'B':1557{1558size_t fno = str.first_not_of('0', 2);1559if (fno == csubstr::npos)1560return false;1561return !(str.len <= fno +(sizeof(T) * 8));1562}1563case 'o':1564case 'O':1565{1566size_t fno = str.first_not_of('0', 2);1567if(fno == csubstr::npos)1568return false;1569return detail::charconv_digits<T>::is_oct_overflow(str.sub(fno));1570}1571default:1572{1573size_t fno = str.first_not_of('0', 1);1574if(fno == csubstr::npos)1575return false;1576return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::max_value_dec());1577}1578}1579}1580else if(C4_UNLIKELY(str[0] == '-'))1581{1582return true;1583}1584else1585{1586return detail::check_overflow(str, detail::charconv_digits<T>::max_value_dec());1587}1588}158915901591/** Test if the following string would overflow when converted to associated1592* types.1593* @return true if number will overflow, false if it fits (or doesn't parse)1594*/1595template<class T>1596auto overflows(csubstr str)1597-> typename std::enable_if<std::is_signed<T>::value, bool>::type1598{1599C4_STATIC_ASSERT(std::is_integral<T>::value);1600if(C4_UNLIKELY(str.len == 0))1601return false;1602if(str.str[0] == '-')1603{1604if(str.str[1] == '0')1605{1606if(str.len == 2)1607return false;1608switch(str.str[2])1609{1610case 'x':1611case 'X':1612{1613size_t fno = str.first_not_of('0', 3);1614if (fno == csubstr::npos)1615return false;1616return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_hex());1617}1618case 'b':1619case 'B':1620{1621size_t fno = str.first_not_of('0', 3);1622if (fno == csubstr::npos)1623return false;1624return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_bin());1625}1626case 'o':1627case 'O':1628{1629size_t fno = str.first_not_of('0', 3);1630if(fno == csubstr::npos)1631return false;1632return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_oct());1633}1634default:1635{1636size_t fno = str.first_not_of('0', 2);1637if(fno == csubstr::npos)1638return false;1639return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_dec());1640}1641}1642}1643else1644return detail::check_overflow(str.sub(1), detail::charconv_digits<T>::min_value_dec());1645}1646else if(str.str[0] == '0')1647{1648if (str.len == 1)1649return false;1650switch(str.str[1])1651{1652case 'x':1653case 'X':1654{1655size_t fno = str.first_not_of('0', 2);1656if (fno == csubstr::npos)1657return false;1658const size_t len = str.len - fno;1659return !((len < sizeof (T) * 2) || (len == sizeof(T) * 2 && str[fno] <= '7'));1660}1661case 'b':1662case 'B':1663{1664size_t fno = str.first_not_of('0', 2);1665if (fno == csubstr::npos)1666return false;1667return !(str.len <= fno + (sizeof(T) * 8 - 1));1668}1669case 'o':1670case 'O':1671{1672size_t fno = str.first_not_of('0', 2);1673if(fno == csubstr::npos)1674return false;1675return detail::charconv_digits<T>::is_oct_overflow(str.sub(fno));1676}1677default:1678{1679size_t fno = str.first_not_of('0', 1);1680if(fno == csubstr::npos)1681return false;1682return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::max_value_dec());1683}1684}1685}1686else1687return detail::check_overflow(str, detail::charconv_digits<T>::max_value_dec());1688}168916901691//-----------------------------------------------------------------------------1692//-----------------------------------------------------------------------------1693//-----------------------------------------------------------------------------16941695namespace detail {169616971698#if (!C4CORE_HAVE_STD_FROMCHARS)1699/** @see http://www.exploringbinary.com/ for many good examples on float-str conversion */1700template<size_t N>1701void get_real_format_str(char (& C4_RESTRICT fmt)[N], int precision, RealFormat_e formatting, const char* length_modifier="")1702{1703int iret;1704if(precision == -1)1705iret = snprintf(fmt, sizeof(fmt), "%%%s%c", length_modifier, formatting);1706else if(precision == 0)1707iret = snprintf(fmt, sizeof(fmt), "%%.%s%c", length_modifier, formatting);1708else1709iret = snprintf(fmt, sizeof(fmt), "%%.%d%s%c", precision, length_modifier, formatting);1710C4_ASSERT(iret >= 2 && size_t(iret) < sizeof(fmt));1711C4_UNUSED(iret);1712}171317141715/** @todo we're depending on snprintf()/sscanf() for converting to/from1716* floating point numbers. Apparently, this increases the binary size1717* by a considerable amount. There are some lightweight printf1718* implementations:1719*1720* @see http://www.sparetimelabs.com/tinyprintf/tinyprintf.php (BSD)1721* @see https://github.com/weiss/c99-snprintf1722* @see https://github.com/nothings/stb/blob/master/stb_sprintf.h1723* @see http://www.exploringbinary.com/1724* @see https://blog.benoitblanchon.fr/lightweight-float-to-string/1725* @see http://www.ryanjuckett.com/programming/printing-floating-point-numbers/1726*/1727template<class T>1728size_t print_one(substr str, const char* full_fmt, T v)1729{1730#ifdef _MSC_VER1731/** use _snprintf() to prevent early termination of the output1732* for writing the null character at the last position1733* @see https://msdn.microsoft.com/en-us/library/2ts7cx93.aspx */1734int iret = _snprintf(str.str, str.len, full_fmt, v);1735if(iret < 0)1736{1737/* when buf.len is not enough, VS returns a negative value.1738* so call it again with a negative value for getting an1739* actual length of the string */1740iret = snprintf(nullptr, 0, full_fmt, v);1741C4_ASSERT(iret > 0);1742}1743size_t ret = (size_t) iret;1744return ret;1745#else1746int iret = snprintf(str.str, str.len, full_fmt, v);1747C4_ASSERT(iret >= 0);1748size_t ret = (size_t) iret;1749if(ret >= str.len)1750++ret; /* snprintf() reserves the last character to write \0 */1751return ret;1752#endif1753}1754#endif // (!C4CORE_HAVE_STD_FROMCHARS)175517561757#if (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT)1758/** scans a string using the given type format, while at the same time1759* allowing non-null-terminated strings AND guaranteeing that the given1760* string length is strictly respected, so that no buffer overflows1761* might occur. */1762template<typename T>1763inline size_t scan_one(csubstr str, const char *type_fmt, T *v)1764{1765/* snscanf() is absolutely needed here as we must be sure that1766* str.len is strictly respected, because substr is1767* generally not null-terminated.1768*1769* Alas, there is no snscanf().1770*1771* So we fake it by using a dynamic format with an explicit1772* field size set to the length of the given span.1773* This trick is taken from:1774* https://stackoverflow.com/a/18368910/5875572 */17751776/* this is the actual format we'll use for scanning */1777char fmt[16];17781779/* write the length into it. Eg "%12f".1780* Also, get the number of characters read from the string.1781* So the final format ends up as "%12f%n"*/1782int iret = std::snprintf(fmt, sizeof(fmt), "%%" "%zu" "%s" "%%n", str.len, type_fmt);1783/* no nasty surprises, please! */1784C4_ASSERT(iret >= 0 && size_t(iret) < C4_COUNTOF(fmt));17851786/* now we scan with confidence that the span length is respected */1787int num_chars;1788iret = std::sscanf(str.str, fmt, v, &num_chars);1789/* scanf returns the number of successful conversions */1790if(iret != 1) return csubstr::npos;1791C4_ASSERT(num_chars >= 0);1792return (size_t)(num_chars);1793}1794#endif // (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT)179517961797#if C4CORE_HAVE_STD_TOCHARS1798template<class T>1799C4_ALWAYS_INLINE size_t rtoa(substr buf, T v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept1800{1801std::to_chars_result result;1802size_t pos = 0;1803if(formatting == FTOA_HEXA)1804{1805if(buf.len > size_t(2))1806{1807buf.str[0] = '0';1808buf.str[1] = 'x';1809}1810pos += size_t(2);1811}1812if(precision == -1)1813result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting);1814else1815result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting, precision);1816if(result.ec == std::errc())1817{1818// all good, no errors.1819C4_ASSERT(result.ptr >= buf.str);1820ptrdiff_t delta = result.ptr - buf.str;1821return static_cast<size_t>(delta);1822}1823C4_ASSERT(result.ec == std::errc::value_too_large);1824// This is unfortunate.1825//1826// When the result can't fit in the given buffer,1827// std::to_chars() returns the end pointer it was originally1828// given, which is useless because here we would like to know1829// _exactly_ how many characters the buffer must have to fit1830// the result.1831//1832// So we take the pessimistic view, and assume as many digits1833// as could ever be required:1834size_t ret = static_cast<size_t>(std::numeric_limits<T>::max_digits10);1835return ret > buf.len ? ret : buf.len + 1;1836}1837#endif // C4CORE_HAVE_STD_TOCHARS183818391840#if C4CORE_HAVE_FAST_FLOAT1841template<class T>1842C4_ALWAYS_INLINE bool scan_rhex(csubstr s, T *C4_RESTRICT val) noexcept1843{1844C4_ASSERT(s.len > 0);1845C4_ASSERT(s.str[0] != '-');1846C4_ASSERT(s.str[0] != '+');1847C4_ASSERT(!s.begins_with("0x"));1848C4_ASSERT(!s.begins_with("0X"));1849size_t pos = 0;1850// integer part1851for( ; pos < s.len; ++pos)1852{1853const char c = s.str[pos];1854if(c >= '0' && c <= '9')1855*val = *val * T(16) + T(c - '0');1856else if(c >= 'a' && c <= 'f')1857*val = *val * T(16) + T(c - 'a');1858else if(c >= 'A' && c <= 'F')1859*val = *val * T(16) + T(c - 'A');1860else if(c == '.')1861{1862++pos;1863break; // follow on to mantissa1864}1865else if(c == 'p' || c == 'P')1866{1867++pos;1868goto power; // no mantissa given, jump to power1869}1870else1871{1872return false;1873}1874}1875// mantissa1876{1877// 0.0625 == 1/16 == value of first digit after the comma1878for(T digit = T(0.0625); pos < s.len; ++pos, digit /= T(16))1879{1880const char c = s.str[pos];1881if(c >= '0' && c <= '9')1882*val += digit * T(c - '0');1883else if(c >= 'a' && c <= 'f')1884*val += digit * T(c - 'a');1885else if(c >= 'A' && c <= 'F')1886*val += digit * T(c - 'A');1887else if(c == 'p' || c == 'P')1888{1889++pos;1890goto power; // mantissa finished, jump to power1891}1892else1893{1894return false;1895}1896}1897}1898return true;1899power:1900if(C4_LIKELY(pos < s.len))1901{1902if(s.str[pos] == '+') // atoi() cannot handle a leading '+'1903++pos;1904if(C4_LIKELY(pos < s.len))1905{1906int16_t powval = {};1907if(C4_LIKELY(atoi(s.sub(pos), &powval)))1908{1909*val *= ipow<T, int16_t, 16>(powval);1910return true;1911}1912}1913}1914return false;1915}1916#endif19171918} // namespace detail191919201921#undef _c4appendhex1922#undef _c4append192319241925/** Convert a single-precision real number to string. The string will1926* in general be NOT null-terminated. For FTOA_FLEX, \p precision is1927* the number of significand digits. Otherwise \p precision is the1928* number of decimals. It is safe to call this function with an empty1929* or too-small buffer.1930*1931* @return the size of the buffer needed to write the number1932*/1933C4_ALWAYS_INLINE size_t ftoa(substr str, float v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept1934{1935#if C4CORE_HAVE_STD_TOCHARS1936return detail::rtoa(str, v, precision, formatting);1937#else1938char fmt[16];1939detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/"");1940return detail::print_one(str, fmt, v);1941#endif1942}194319441945/** Convert a double-precision real number to string. The string will1946* in general be NOT null-terminated. For FTOA_FLEX, \p precision is1947* the number of significand digits. Otherwise \p precision is the1948* number of decimals. It is safe to call this function with an empty1949* or too-small buffer.1950*1951* @return the size of the buffer needed to write the number1952*/1953C4_ALWAYS_INLINE size_t dtoa(substr str, double v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept1954{1955#if C4CORE_HAVE_STD_TOCHARS1956return detail::rtoa(str, v, precision, formatting);1957#else1958char fmt[16];1959detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/"l");1960return detail::print_one(str, fmt, v);1961#endif1962}196319641965/** Convert a string to a single precision real number.1966* The input string must be trimmed to the value, ie1967* no leading or trailing whitespace can be present.1968* @return true iff the conversion succeeded1969* @see atof_first() if the string is not trimmed1970*/1971C4_ALWAYS_INLINE bool atof(csubstr str, float * C4_RESTRICT v) noexcept1972{1973C4_ASSERT(str.len > 0);1974C4_ASSERT(str.triml(" \r\t\n").len == str.len);1975#if C4CORE_HAVE_FAST_FLOAT1976// fastfloat cannot parse hexadecimal floats1977bool isneg = (str.str[0] == '-');1978csubstr rem = str.sub(isneg || str.str[0] == '+');1979if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X'))))1980{1981fast_float::from_chars_result result;1982result = fast_float::from_chars(str.str, str.str + str.len, *v);1983return result.ec == std::errc();1984}1985else if(detail::scan_rhex(rem.sub(2), v))1986{1987*v *= isneg ? -1.f : 1.f;1988return true;1989}1990return false;1991#elif C4CORE_HAVE_STD_FROMCHARS1992std::from_chars_result result;1993result = std::from_chars(str.str, str.str + str.len, *v);1994return result.ec == std::errc();1995#else1996csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+');1997if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X'))))1998return detail::scan_one(str, "f", v) != csubstr::npos;1999else2000return detail::scan_one(str, "a", v) != csubstr::npos;2001#endif2002}200320042005/** Convert a string to a double precision real number.2006* The input string must be trimmed to the value, ie2007* no leading or trailing whitespace can be present.2008* @return true iff the conversion succeeded2009* @see atod_first() if the string is not trimmed2010*/2011C4_ALWAYS_INLINE bool atod(csubstr str, double * C4_RESTRICT v) noexcept2012{2013C4_ASSERT(str.triml(" \r\t\n").len == str.len);2014#if C4CORE_HAVE_FAST_FLOAT2015// fastfloat cannot parse hexadecimal floats2016bool isneg = (str.str[0] == '-');2017csubstr rem = str.sub(isneg || str.str[0] == '+');2018if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X'))))2019{2020fast_float::from_chars_result result;2021result = fast_float::from_chars(str.str, str.str + str.len, *v);2022return result.ec == std::errc();2023}2024else if(detail::scan_rhex(rem.sub(2), v))2025{2026*v *= isneg ? -1. : 1.;2027return true;2028}2029return false;2030#elif C4CORE_HAVE_STD_FROMCHARS2031std::from_chars_result result;2032result = std::from_chars(str.str, str.str + str.len, *v);2033return result.ec == std::errc();2034#else2035csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+');2036if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X'))))2037return detail::scan_one(str, "lf", v) != csubstr::npos;2038else2039return detail::scan_one(str, "la", v) != csubstr::npos;2040#endif2041}204220432044/** Convert a string to a single precision real number.2045* Leading whitespace is skipped until valid characters are found.2046* @return the number of characters read from the string, or npos if2047* conversion was not successful or if the string was empty */2048inline size_t atof_first(csubstr str, float * C4_RESTRICT v) noexcept2049{2050csubstr trimmed = str.first_real_span();2051if(trimmed.len == 0)2052return csubstr::npos;2053if(atof(trimmed, v))2054return static_cast<size_t>(trimmed.end() - str.begin());2055return csubstr::npos;2056}205720582059/** Convert a string to a double precision real number.2060* Leading whitespace is skipped until valid characters are found.2061* @return the number of characters read from the string, or npos if2062* conversion was not successful or if the string was empty */2063inline size_t atod_first(csubstr str, double * C4_RESTRICT v) noexcept2064{2065csubstr trimmed = str.first_real_span();2066if(trimmed.len == 0)2067return csubstr::npos;2068if(atod(trimmed, v))2069return static_cast<size_t>(trimmed.end() - str.begin());2070return csubstr::npos;2071}207220732074//-----------------------------------------------------------------------------2075//-----------------------------------------------------------------------------2076//-----------------------------------------------------------------------------2077// generic versions20782079C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v) noexcept { return write_dec(s, v); }2080C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v) noexcept { return write_dec(s, v); }2081C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v) noexcept { return write_dec(s, v); }2082C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v) noexcept { return write_dec(s, v); }2083C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v) noexcept { return itoa(s, v); }2084C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v) noexcept { return itoa(s, v); }2085C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v) noexcept { return itoa(s, v); }2086C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v) noexcept { return itoa(s, v); }2087C4_ALWAYS_INLINE size_t xtoa(substr s, float v) noexcept { return ftoa(s, v); }2088C4_ALWAYS_INLINE size_t xtoa(substr s, double v) noexcept { return dtoa(s, v); }20892090C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v, uint8_t radix) noexcept { return utoa(s, v, radix); }2091C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v, uint16_t radix) noexcept { return utoa(s, v, radix); }2092C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v, uint32_t radix) noexcept { return utoa(s, v, radix); }2093C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v, uint64_t radix) noexcept { return utoa(s, v, radix); }2094C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v, int8_t radix) noexcept { return itoa(s, v, radix); }2095C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v, int16_t radix) noexcept { return itoa(s, v, radix); }2096C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v, int32_t radix) noexcept { return itoa(s, v, radix); }2097C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v, int64_t radix) noexcept { return itoa(s, v, radix); }20982099C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v, uint8_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); }2100C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v, uint16_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); }2101C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v, uint32_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); }2102C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v, uint64_t radix, size_t num_digits) noexcept { return utoa(s, v, radix, num_digits); }2103C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v, int8_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); }2104C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v, int16_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); }2105C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v, int32_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); }2106C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v, int64_t radix, size_t num_digits) noexcept { return itoa(s, v, radix, num_digits); }21072108C4_ALWAYS_INLINE size_t xtoa(substr s, float v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return ftoa(s, v, precision, formatting); }2109C4_ALWAYS_INLINE size_t xtoa(substr s, double v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return dtoa(s, v, precision, formatting); }21102111C4_ALWAYS_INLINE bool atox(csubstr s, uint8_t *C4_RESTRICT v) noexcept { return atou(s, v); }2112C4_ALWAYS_INLINE bool atox(csubstr s, uint16_t *C4_RESTRICT v) noexcept { return atou(s, v); }2113C4_ALWAYS_INLINE bool atox(csubstr s, uint32_t *C4_RESTRICT v) noexcept { return atou(s, v); }2114C4_ALWAYS_INLINE bool atox(csubstr s, uint64_t *C4_RESTRICT v) noexcept { return atou(s, v); }2115C4_ALWAYS_INLINE bool atox(csubstr s, int8_t *C4_RESTRICT v) noexcept { return atoi(s, v); }2116C4_ALWAYS_INLINE bool atox(csubstr s, int16_t *C4_RESTRICT v) noexcept { return atoi(s, v); }2117C4_ALWAYS_INLINE bool atox(csubstr s, int32_t *C4_RESTRICT v) noexcept { return atoi(s, v); }2118C4_ALWAYS_INLINE bool atox(csubstr s, int64_t *C4_RESTRICT v) noexcept { return atoi(s, v); }2119C4_ALWAYS_INLINE bool atox(csubstr s, float *C4_RESTRICT v) noexcept { return atof(s, v); }2120C4_ALWAYS_INLINE bool atox(csubstr s, double *C4_RESTRICT v) noexcept { return atod(s, v); }21212122C4_ALWAYS_INLINE size_t to_chars(substr buf, uint8_t v) noexcept { return write_dec(buf, v); }2123C4_ALWAYS_INLINE size_t to_chars(substr buf, uint16_t v) noexcept { return write_dec(buf, v); }2124C4_ALWAYS_INLINE size_t to_chars(substr buf, uint32_t v) noexcept { return write_dec(buf, v); }2125C4_ALWAYS_INLINE size_t to_chars(substr buf, uint64_t v) noexcept { return write_dec(buf, v); }2126C4_ALWAYS_INLINE size_t to_chars(substr buf, int8_t v) noexcept { return itoa(buf, v); }2127C4_ALWAYS_INLINE size_t to_chars(substr buf, int16_t v) noexcept { return itoa(buf, v); }2128C4_ALWAYS_INLINE size_t to_chars(substr buf, int32_t v) noexcept { return itoa(buf, v); }2129C4_ALWAYS_INLINE size_t to_chars(substr buf, int64_t v) noexcept { return itoa(buf, v); }2130C4_ALWAYS_INLINE size_t to_chars(substr buf, float v) noexcept { return ftoa(buf, v); }2131C4_ALWAYS_INLINE size_t to_chars(substr buf, double v) noexcept { return dtoa(buf, v); }21322133C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint8_t *C4_RESTRICT v) noexcept { return atou(buf, v); }2134C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou(buf, v); }2135C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou(buf, v); }2136C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou(buf, v); }2137C4_ALWAYS_INLINE bool from_chars(csubstr buf, int8_t *C4_RESTRICT v) noexcept { return atoi(buf, v); }2138C4_ALWAYS_INLINE bool from_chars(csubstr buf, int16_t *C4_RESTRICT v) noexcept { return atoi(buf, v); }2139C4_ALWAYS_INLINE bool from_chars(csubstr buf, int32_t *C4_RESTRICT v) noexcept { return atoi(buf, v); }2140C4_ALWAYS_INLINE bool from_chars(csubstr buf, int64_t *C4_RESTRICT v) noexcept { return atoi(buf, v); }2141C4_ALWAYS_INLINE bool from_chars(csubstr buf, float *C4_RESTRICT v) noexcept { return atof(buf, v); }2142C4_ALWAYS_INLINE bool from_chars(csubstr buf, double *C4_RESTRICT v) noexcept { return atod(buf, v); }21432144C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint8_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); }2145C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); }2146C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); }2147C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); }2148C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int8_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); }2149C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int16_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); }2150C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int32_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); }2151C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int64_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); }2152C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, float *C4_RESTRICT v) noexcept { return atof_first(buf, v); }2153C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, double *C4_RESTRICT v) noexcept { return atod_first(buf, v); }215421552156//-----------------------------------------------------------------------------2157// on some platforms, (unsigned) int and (unsigned) long2158// are not any of the fixed length types above21592160#define _C4_IF_NOT_FIXED_LENGTH_I(T, ty) C4_ALWAYS_INLINE typename std::enable_if<std:: is_signed<T>::value && !is_fixed_length<T>::value_i, ty>2161#define _C4_IF_NOT_FIXED_LENGTH_U(T, ty) C4_ALWAYS_INLINE typename std::enable_if<std::is_unsigned<T>::value && !is_fixed_length<T>::value_u, ty>21622163template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type xtoa(substr buf, T v) noexcept { return itoa(buf, v); }2164template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type xtoa(substr buf, T v) noexcept { return write_dec(buf, v); }21652166template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, bool )::type atox(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi(buf, v); }2167template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, bool )::type atox(csubstr buf, T *C4_RESTRICT v) noexcept { return atou(buf, v); }21682169template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type to_chars(substr buf, T v) noexcept { return itoa(buf, v); }2170template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type to_chars(substr buf, T v) noexcept { return write_dec(buf, v); }21712172template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, bool )::type from_chars(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi(buf, v); }2173template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, bool )::type from_chars(csubstr buf, T *C4_RESTRICT v) noexcept { return atou(buf, v); }21742175template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type from_chars_first(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi_first(buf, v); }2176template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type from_chars_first(csubstr buf, T *C4_RESTRICT v) noexcept { return atou_first(buf, v); }21772178#undef _C4_IF_NOT_FIXED_LENGTH_I2179#undef _C4_IF_NOT_FIXED_LENGTH_U218021812182//-----------------------------------------------------------------------------2183// for pointers21842185template <class T> C4_ALWAYS_INLINE size_t xtoa(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); }2186template <class T> C4_ALWAYS_INLINE bool atox(csubstr s, T **v) noexcept { intptr_t tmp; bool ret = atox(s, &tmp); if(ret) { *v = (T*)tmp; } return ret; }2187template <class T> C4_ALWAYS_INLINE size_t to_chars(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); }2188template <class T> C4_ALWAYS_INLINE bool from_chars(csubstr buf, T **v) noexcept { intptr_t tmp; bool ret = from_chars(buf, &tmp); if(ret) { *v = (T*)tmp; } return ret; }2189template <class T> C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, T **v) noexcept { intptr_t tmp; bool ret = from_chars_first(buf, &tmp); if(ret) { *v = (T*)tmp; } return ret; }219021912192//-----------------------------------------------------------------------------2193//-----------------------------------------------------------------------------2194//-----------------------------------------------------------------------------2195/** call to_chars() and return a substr consisting of the2196* written portion of the input buffer. Ie, same as to_chars(),2197* but return a substr instead of a size_t.2198*2199* @see to_chars() */2200template<class T>2201C4_ALWAYS_INLINE substr to_chars_sub(substr buf, T const& C4_RESTRICT v) noexcept2202{2203size_t sz = to_chars(buf, v);2204return buf.left_of(sz <= buf.len ? sz : buf.len);2205}22062207//-----------------------------------------------------------------------------2208//-----------------------------------------------------------------------------2209//-----------------------------------------------------------------------------2210// bool implementation22112212C4_ALWAYS_INLINE size_t to_chars(substr buf, bool v) noexcept2213{2214int val = v;2215return to_chars(buf, val);2216}22172218inline bool from_chars(csubstr buf, bool * C4_RESTRICT v) noexcept2219{2220if(buf == '0')2221{2222*v = false; return true;2223}2224else if(buf == '1')2225{2226*v = true; return true;2227}2228else if(buf == "false")2229{2230*v = false; return true;2231}2232else if(buf == "true")2233{2234*v = true; return true;2235}2236else if(buf == "False")2237{2238*v = false; return true;2239}2240else if(buf == "True")2241{2242*v = true; return true;2243}2244else if(buf == "FALSE")2245{2246*v = false; return true;2247}2248else if(buf == "TRUE")2249{2250*v = true; return true;2251}2252// fallback to c-style int bools2253int val = 0;2254bool ret = from_chars(buf, &val);2255if(C4_LIKELY(ret))2256{2257*v = (val != 0);2258}2259return ret;2260}22612262inline size_t from_chars_first(csubstr buf, bool * C4_RESTRICT v) noexcept2263{2264csubstr trimmed = buf.first_non_empty_span();2265if(trimmed.len == 0 || !from_chars(buf, v))2266return csubstr::npos;2267return trimmed.len;2268}226922702271//-----------------------------------------------------------------------------2272// single-char implementation22732274inline size_t to_chars(substr buf, char v) noexcept2275{2276if(buf.len > 0)2277{2278C4_XASSERT(buf.str);2279buf.str[0] = v;2280}2281return 1;2282}22832284/** extract a single character from a substring2285* @note to extract a string instead and not just a single character, use the csubstr overload */2286inline bool from_chars(csubstr buf, char * C4_RESTRICT v) noexcept2287{2288if(buf.len != 1)2289return false;2290C4_XASSERT(buf.str);2291*v = buf.str[0];2292return true;2293}22942295inline size_t from_chars_first(csubstr buf, char * C4_RESTRICT v) noexcept2296{2297if(buf.len < 1)2298return csubstr::npos;2299*v = buf.str[0];2300return 1;2301}230223032304//-----------------------------------------------------------------------------2305// csubstr implementation23062307inline size_t to_chars(substr buf, csubstr v) noexcept2308{2309C4_ASSERT(!buf.overlaps(v));2310size_t len = buf.len < v.len ? buf.len : v.len;2311// calling memcpy with null strings is undefined behavior2312// and will wreak havoc in calling code's branches.2313// see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-12621336372314if(len)2315{2316C4_ASSERT(buf.str != nullptr);2317C4_ASSERT(v.str != nullptr);2318memcpy(buf.str, v.str, len);2319}2320return v.len;2321}23222323inline bool from_chars(csubstr buf, csubstr *C4_RESTRICT v) noexcept2324{2325*v = buf;2326return true;2327}23282329inline size_t from_chars_first(substr buf, csubstr * C4_RESTRICT v) noexcept2330{2331csubstr trimmed = buf.first_non_empty_span();2332if(trimmed.len == 0)2333return csubstr::npos;2334*v = trimmed;2335return static_cast<size_t>(trimmed.end() - buf.begin());2336}233723382339//-----------------------------------------------------------------------------2340// substr23412342inline size_t to_chars(substr buf, substr v) noexcept2343{2344C4_ASSERT(!buf.overlaps(v));2345size_t len = buf.len < v.len ? buf.len : v.len;2346// calling memcpy with null strings is undefined behavior2347// and will wreak havoc in calling code's branches.2348// see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-12621336372349if(len)2350{2351C4_ASSERT(buf.str != nullptr);2352C4_ASSERT(v.str != nullptr);2353memcpy(buf.str, v.str, len);2354}2355return v.len;2356}23572358inline bool from_chars(csubstr buf, substr * C4_RESTRICT v) noexcept2359{2360C4_ASSERT(!buf.overlaps(*v));2361// is the destination buffer wide enough?2362if(v->len >= buf.len)2363{2364// calling memcpy with null strings is undefined behavior2365// and will wreak havoc in calling code's branches.2366// see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-12621336372367if(buf.len)2368{2369C4_ASSERT(buf.str != nullptr);2370C4_ASSERT(v->str != nullptr);2371memcpy(v->str, buf.str, buf.len);2372}2373v->len = buf.len;2374return true;2375}2376return false;2377}23782379inline size_t from_chars_first(csubstr buf, substr * C4_RESTRICT v) noexcept2380{2381csubstr trimmed = buf.first_non_empty_span();2382C4_ASSERT(!trimmed.overlaps(*v));2383if(C4_UNLIKELY(trimmed.len == 0))2384return csubstr::npos;2385size_t len = trimmed.len > v->len ? v->len : trimmed.len;2386// calling memcpy with null strings is undefined behavior2387// and will wreak havoc in calling code's branches.2388// see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-12621336372389if(len)2390{2391C4_ASSERT(buf.str != nullptr);2392C4_ASSERT(v->str != nullptr);2393memcpy(v->str, trimmed.str, len);2394}2395if(C4_UNLIKELY(trimmed.len > v->len))2396return csubstr::npos;2397return static_cast<size_t>(trimmed.end() - buf.begin());2398}239924002401//-----------------------------------------------------------------------------24022403template<size_t N>2404inline size_t to_chars(substr buf, const char (& C4_RESTRICT v)[N]) noexcept2405{2406csubstr sp(v);2407return to_chars(buf, sp);2408}24092410inline size_t to_chars(substr buf, const char * C4_RESTRICT v) noexcept2411{2412return to_chars(buf, to_csubstr(v));2413}24142415} // namespace c424162417#ifdef _MSC_VER2418# pragma warning(pop)2419#endif24202421#if defined(__clang__)2422# pragma clang diagnostic pop2423#elif defined(__GNUC__)2424# pragma GCC diagnostic pop2425#endif24262427#endif /* _C4_CHARCONV_HPP_ */242824292430