#ifndef _C4_CHARCONV_HPP_ #define _C4_CHARCONV_HPP_ /** @file charconv.hpp Lightweight generic type-safe wrappers for * converting individual values to/from strings. */ #include "c4/language.hpp" #include #include #include #include #include #include "c4/config.hpp" #include "c4/substr.hpp" #include "c4/std/std_fwd.hpp" #include "c4/memory_util.hpp" #include "c4/szconv.hpp" #ifndef C4CORE_NO_FAST_FLOAT # if (C4_CPP >= 17) # if defined(_MSC_VER) # if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros # include # define C4CORE_HAVE_STD_TOCHARS 1 # define C4CORE_HAVE_STD_FROMCHARS 0 // prefer fast_float with MSVC # define C4CORE_HAVE_FAST_FLOAT 1 # else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # define C4CORE_HAVE_FAST_FLOAT 1 # endif # else # if __has_include() # include # if defined(__cpp_lib_to_chars) # define C4CORE_HAVE_STD_TOCHARS 1 # define C4CORE_HAVE_STD_FROMCHARS 0 // glibc uses fast_float internally # define C4CORE_HAVE_FAST_FLOAT 1 # else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # define C4CORE_HAVE_FAST_FLOAT 1 # endif # else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # define C4CORE_HAVE_FAST_FLOAT 1 # endif # endif # else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # define C4CORE_HAVE_FAST_FLOAT 1 # endif # if C4CORE_HAVE_FAST_FLOAT C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wsign-conversion") C4_SUPPRESS_WARNING_GCC("-Warray-bounds") # if defined(__GNUC__) && __GNUC__ >= 5 C4_SUPPRESS_WARNING_GCC("-Wshift-count-overflow") # endif //# include "c4/ext/fast_float.hpp" #include "fast_float/fast_float.h" C4_SUPPRESS_WARNING_GCC_POP # endif #elif (C4_CPP >= 17) # define C4CORE_HAVE_FAST_FLOAT 0 # if defined(_MSC_VER) # if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros # include # define C4CORE_HAVE_STD_TOCHARS 1 # define C4CORE_HAVE_STD_FROMCHARS 1 # else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # endif # else # if __has_include() # include # if defined(__cpp_lib_to_chars) # define C4CORE_HAVE_STD_TOCHARS 1 # define C4CORE_HAVE_STD_FROMCHARS 1 // glibc uses fast_float internally # else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # endif # else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # endif # endif #else # define C4CORE_HAVE_STD_TOCHARS 0 # define C4CORE_HAVE_STD_FROMCHARS 0 # define C4CORE_HAVE_FAST_FLOAT 0 #endif #if !C4CORE_HAVE_STD_FROMCHARS #include #endif #if defined(_MSC_VER) # pragma warning(push) # pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe # if C4_MSVC_VERSION != C4_MSVC_VERSION_2017 # pragma warning(disable: 4800) //'int': forcing value to bool 'true' or 'false' (performance warning) # endif #endif #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" # pragma clang diagnostic ignored "-Wformat-nonliteral" # pragma clang diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision # pragma clang diagnostic ignored "-Wold-style-cast" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wformat-nonliteral" # pragma GCC diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision # pragma GCC diagnostic ignored "-Wuseless-cast" # pragma GCC diagnostic ignored "-Wold-style-cast" #endif #if defined(__clang__) #define C4_NO_UBSAN_IOVRFLW __attribute__((no_sanitize("signed-integer-overflow"))) #elif defined(__GNUC__) #if __GNUC__ > 7 #define C4_NO_UBSAN_IOVRFLW __attribute__((no_sanitize("signed-integer-overflow"))) #else #define C4_NO_UBSAN_IOVRFLW #endif #else #define C4_NO_UBSAN_IOVRFLW #endif namespace c4 { /** @defgroup doc_charconv Charconv utilities * * Lightweight, very fast generic type-safe wrappers for converting * individual values to/from strings. These are the main generic * functions: * - @ref doc_to_chars and its alias @ref doc_xtoa: implemented by calling @ref itoa()/@ref utoa()/@ref ftoa()/@ref dtoa() (or generically @ref xtoa()) * - @ref doc_from_chars and its alias @ref doc_atox: implemented by calling @ref atoi()/@ref atou()/@ref atof()/@ref atod() (or generically @ref atox()) * - @ref to_chars_sub() * - @ref from_chars_first() * - @ref xtoa()/@ref atox() are implemented in terms @ref write_dec()/@ref read_dec() et al (see @ref doc_write/@ref doc_read()) * * And also some modest brag is in order: these functions are really * fast: faster even than C++17 `std::to_chars()` and * `std::to_chars()`, and many dozens of times faster than the * iostream abominations. * * For example, here are some benchmark comparisons for @ref * doc_from_chars (link leads to the main project README, where these * results are shown more systematically). * * * *
atox,int64_t
g++12, linux Visual Studio 2019 *
\image html linux-x86_64-gxx12.1-Release-c4core-bm-charconv-atox-mega_bytes_per_second-i64.png \image html windows-x86_64-vs2019-Release-c4core-bm-charconv-atox-mega_bytes_per_second-i64.png *
* * * *
xtoa,int64_t
g++12, linux Visual Studio 2019 *
\image html linux-x86_64-gxx12.1-Release-c4core-bm-charconv-xtoa-mega_bytes_per_second-i64.png \image html windows-x86_64-vs2019-Release-c4core-bm-charconv-xtoa-mega_bytes_per_second-i64.png *
* * To parse floating point, c4core uses * [fastfloat](https://github.com/fastfloat/fast_float), which is * extremely fast, by an even larger factor: * * * *
atox,float
g++12, linux Visual Studio 2019 *
\image html linux-x86_64-gxx12.1-Release-c4core-bm-charconv-atof-mega_bytes_per_second-float.png \image html windows-x86_64-vs2019-Release-c4core-bm-charconv-atof-mega_bytes_per_second-float.png *
* * @{ */ #if C4CORE_HAVE_STD_TOCHARS /** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */ typedef enum : std::underlying_type::type { /** print the real number in floating point format (like %f) */ FTOA_FLOAT = static_cast::type>(std::chars_format::fixed), /** print the real number in scientific format (like %e) */ FTOA_SCIENT = static_cast::type>(std::chars_format::scientific), /** print the real number in flexible format (like %g) */ FTOA_FLEX = static_cast::type>(std::chars_format::general), /** print the real number in hexadecimal format (like %a) */ FTOA_HEXA = static_cast::type>(std::chars_format::hex), } RealFormat_e; #else /** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */ typedef enum : char { /** print the real number in floating point format (like %f) */ FTOA_FLOAT = 'f', /** print the real number in scientific format (like %e) */ FTOA_SCIENT = 'e', /** print the real number in flexible format (like %g) */ FTOA_FLEX = 'g', /** print the real number in hexadecimal format (like %a) */ FTOA_HEXA = 'a', } RealFormat_e; #endif /** @cond dev */ /** in some platforms, int,unsigned int * are not any of int8_t...int64_t and * long,unsigned long are not any of uint8_t...uint64_t */ template struct is_fixed_length { enum : bool { /** true if T is one of the fixed length signed types */ value_i = (std::is_integral::value && (std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value)), /** true if T is one of the fixed length unsigned types */ value_u = (std::is_integral::value && (std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value)), /** true if T is one of the fixed length signed or unsigned types */ value = value_i || value_u }; }; /** @endcond */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- #ifdef _MSC_VER # pragma warning(push) #elif defined(__clang__) # pragma clang diagnostic push #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wconversion" # if __GNUC__ >= 6 # pragma GCC diagnostic ignored "-Wnull-dereference" # endif #endif /** @cond dev */ namespace detail { /* python command to get the values below: def dec(v): return str(v) for bits in (8, 16, 32, 64): imin, imax, umax = (-(1 << (bits - 1))), (1 << (bits - 1)) - 1, (1 << bits) - 1 for vname, v in (("imin", imin), ("imax", imax), ("umax", umax)): for f in (bin, oct, dec, hex): print(f"{bits}b: {vname}={v} {f.__name__}: len={len(f(v)):2d}: {v} {f(v)}") */ // do not use the type as the template argument because in some // platforms long!=int32 and long!=int64. Just use the numbytes // which is more generic and spares lengthy SFINAE code. template struct charconv_digits_; template using charconv_digits = charconv_digits_::value>; template<> struct charconv_digits_<1u, true> // int8_t { enum : size_t { maxdigits_bin = 1 + 2 + 8, // -128==-0b10000000 maxdigits_oct = 1 + 2 + 3, // -128==-0o200 maxdigits_dec = 1 + 3, // -128 maxdigits_hex = 1 + 2 + 2, // -128==-0x80 maxdigits_bin_nopfx = 8, // -128==-0b10000000 maxdigits_oct_nopfx = 3, // -128==-0o200 maxdigits_dec_nopfx = 3, // -128 maxdigits_hex_nopfx = 2, // -128==-0x80 }; // min values without sign! static constexpr csubstr min_value_dec() noexcept { return csubstr("128"); } static constexpr csubstr min_value_hex() noexcept { return csubstr("80"); } static constexpr csubstr min_value_oct() noexcept { return csubstr("200"); } static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000"); } static constexpr csubstr max_value_dec() noexcept { return csubstr("127"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str[0] <= '1')); } }; template<> struct charconv_digits_<1u, false> // uint8_t { enum : size_t { maxdigits_bin = 2 + 8, // 255 0b11111111 maxdigits_oct = 2 + 3, // 255 0o377 maxdigits_dec = 3, // 255 maxdigits_hex = 2 + 2, // 255 0xff maxdigits_bin_nopfx = 8, // 255 0b11111111 maxdigits_oct_nopfx = 3, // 255 0o377 maxdigits_dec_nopfx = 3, // 255 maxdigits_hex_nopfx = 2, // 255 0xff }; static constexpr csubstr max_value_dec() noexcept { return csubstr("255"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str[0] <= '3')); } }; template<> struct charconv_digits_<2u, true> // int16_t { enum : size_t { maxdigits_bin = 1 + 2 + 16, // -32768 -0b1000000000000000 maxdigits_oct = 1 + 2 + 6, // -32768 -0o100000 maxdigits_dec = 1 + 5, // -32768 -32768 maxdigits_hex = 1 + 2 + 4, // -32768 -0x8000 maxdigits_bin_nopfx = 16, // -32768 -0b1000000000000000 maxdigits_oct_nopfx = 6, // -32768 -0o100000 maxdigits_dec_nopfx = 5, // -32768 -32768 maxdigits_hex_nopfx = 4, // -32768 -0x8000 }; // min values without sign! static constexpr csubstr min_value_dec() noexcept { return csubstr("32768"); } static constexpr csubstr min_value_hex() noexcept { return csubstr("8000"); } static constexpr csubstr min_value_oct() noexcept { return csubstr("100000"); } static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000"); } static constexpr csubstr max_value_dec() noexcept { return csubstr("32767"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 6)); } }; template<> struct charconv_digits_<2u, false> // uint16_t { enum : size_t { maxdigits_bin = 2 + 16, // 65535 0b1111111111111111 maxdigits_oct = 2 + 6, // 65535 0o177777 maxdigits_dec = 6, // 65535 65535 maxdigits_hex = 2 + 4, // 65535 0xffff maxdigits_bin_nopfx = 16, // 65535 0b1111111111111111 maxdigits_oct_nopfx = 6, // 65535 0o177777 maxdigits_dec_nopfx = 6, // 65535 65535 maxdigits_hex_nopfx = 4, // 65535 0xffff }; static constexpr csubstr max_value_dec() noexcept { return csubstr("65535"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 6) || (str.len == 6 && str[0] <= '1')); } }; template<> struct charconv_digits_<4u, true> // int32_t { enum : size_t { maxdigits_bin = 1 + 2 + 32, // len=35: -2147483648 -0b10000000000000000000000000000000 maxdigits_oct = 1 + 2 + 11, // len=14: -2147483648 -0o20000000000 maxdigits_dec = 1 + 10, // len=11: -2147483648 -2147483648 maxdigits_hex = 1 + 2 + 8, // len=11: -2147483648 -0x80000000 maxdigits_bin_nopfx = 32, // len=35: -2147483648 -0b10000000000000000000000000000000 maxdigits_oct_nopfx = 11, // len=14: -2147483648 -0o20000000000 maxdigits_dec_nopfx = 10, // len=11: -2147483648 -2147483648 maxdigits_hex_nopfx = 8, // len=11: -2147483648 -0x80000000 }; // min values without sign! static constexpr csubstr min_value_dec() noexcept { return csubstr("2147483648"); } static constexpr csubstr min_value_hex() noexcept { return csubstr("80000000"); } static constexpr csubstr min_value_oct() noexcept { return csubstr("20000000000"); } static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000000000000000000000000000"); } static constexpr csubstr max_value_dec() noexcept { return csubstr("2147483647"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str[0] <= '1')); } }; template<> struct charconv_digits_<4u, false> // uint32_t { enum : size_t { maxdigits_bin = 2 + 32, // len=34: 4294967295 0b11111111111111111111111111111111 maxdigits_oct = 2 + 11, // len=13: 4294967295 0o37777777777 maxdigits_dec = 10, // len=10: 4294967295 4294967295 maxdigits_hex = 2 + 8, // len=10: 4294967295 0xffffffff maxdigits_bin_nopfx = 32, // len=34: 4294967295 0b11111111111111111111111111111111 maxdigits_oct_nopfx = 11, // len=13: 4294967295 0o37777777777 maxdigits_dec_nopfx = 10, // len=10: 4294967295 4294967295 maxdigits_hex_nopfx = 8, // len=10: 4294967295 0xffffffff }; static constexpr csubstr max_value_dec() noexcept { return csubstr("4294967295"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str[0] <= '3')); } }; template<> struct charconv_digits_<8u, true> // int32_t { enum : size_t { maxdigits_bin = 1 + 2 + 64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000 maxdigits_oct = 1 + 2 + 22, // len=25: -9223372036854775808 -0o1000000000000000000000 maxdigits_dec = 1 + 19, // len=20: -9223372036854775808 -9223372036854775808 maxdigits_hex = 1 + 2 + 16, // len=19: -9223372036854775808 -0x8000000000000000 maxdigits_bin_nopfx = 64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000 maxdigits_oct_nopfx = 22, // len=25: -9223372036854775808 -0o1000000000000000000000 maxdigits_dec_nopfx = 19, // len=20: -9223372036854775808 -9223372036854775808 maxdigits_hex_nopfx = 16, // len=19: -9223372036854775808 -0x8000000000000000 }; static constexpr csubstr min_value_dec() noexcept { return csubstr("9223372036854775808"); } static constexpr csubstr min_value_hex() noexcept { return csubstr("8000000000000000"); } static constexpr csubstr min_value_oct() noexcept { return csubstr("1000000000000000000000"); } static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000000000000000000000000000000000000000000000000000"); } static constexpr csubstr max_value_dec() noexcept { return csubstr("9223372036854775807"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 22)); } }; template<> struct charconv_digits_<8u, false> { enum : size_t { maxdigits_bin = 2 + 64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111 maxdigits_oct = 2 + 22, // len=24: 18446744073709551615 0o1777777777777777777777 maxdigits_dec = 20, // len=20: 18446744073709551615 18446744073709551615 maxdigits_hex = 2 + 16, // len=18: 18446744073709551615 0xffffffffffffffff maxdigits_bin_nopfx = 64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111 maxdigits_oct_nopfx = 22, // len=24: 18446744073709551615 0o1777777777777777777777 maxdigits_dec_nopfx = 20, // len=20: 18446744073709551615 18446744073709551615 maxdigits_hex_nopfx = 16, // len=18: 18446744073709551615 0xffffffffffffffff }; static constexpr csubstr max_value_dec() noexcept { return csubstr("18446744073709551615"); } static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 22) || (str.len == 22 && str[0] <= '1')); } }; } // namespace detail // Helper macros, undefined below #define _c4append(c) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = static_cast(c); } else { ++pos; } } #define _c4appendhex(i) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = hexchars[i]; } else { ++pos; } } /** @endcond */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_digits Get number of digits * * @note At first sight this code may look heavily branchy and * therefore inefficient. However, measurements revealed this to be * the fastest among the alternatives. * * @see https://github.com/biojppm/c4core/pull/77 * * @{ */ /** decimal digits for 8 bit integers */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE auto digits_dec(T v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); return ((v >= 100) ? 3u : ((v >= 10) ? 2u : 1u)); } /** decimal digits for 16 bit integers */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE auto digits_dec(T v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); return ((v >= 10000) ? 5u : (v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u); } /** decimal digits for 32 bit integers */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE auto digits_dec(T v) noexcept -> typename std::enable_if::type { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); return ((v >= 1000000000) ? 10u : (v >= 100000000) ? 9u : (v >= 10000000) ? 8u : (v >= 1000000) ? 7u : (v >= 100000) ? 6u : (v >= 10000) ? 5u : (v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u); } /** decimal digits for 64 bit integers */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE auto digits_dec(T v) noexcept -> typename std::enable_if::type { // thanks @fargies!!! // https://github.com/biojppm/c4core/pull/77#issuecomment-1063753568 C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); if(v >= 1000000000) // 10 { if(v >= 100000000000000) // 15 [15-20] range { if(v >= 100000000000000000) // 18 (15 + (20 - 15) / 2) { if((typename std::make_unsigned::type)v >= 10000000000000000000u) // 20 return 20u; else return (v >= 1000000000000000000) ? 19u : 18u; } else if(v >= 10000000000000000) // 17 return 17u; else return(v >= 1000000000000000) ? 16u : 15u; } else if(v >= 1000000000000) // 13 return (v >= 10000000000000) ? 14u : 13u; else if(v >= 100000000000) // 12 return 12; else return(v >= 10000000000) ? 11u : 10u; } else if(v >= 10000) // 5 [5-9] range { if(v >= 10000000) // 8 return (v >= 100000000) ? 9u : 8u; else if(v >= 1000000) // 7 return 7; else return (v >= 100000) ? 6u : 5u; } else if(v >= 100) return (v >= 1000) ? 4u : 3u; else return (v >= 10) ? 2u : 1u; } /** return the number of digits required to encode an hexadecimal number. */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_hex(T v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); return v ? 1u + (msb((typename std::make_unsigned::type)v) >> 2u) : 1u; } /** return the number of digits required to encode a binary number. */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_bin(T v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); return v ? 1u + msb((typename std::make_unsigned::type)v) : 1u; } /** return the number of digits required to encode an octal number. */ template C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_oct(T v_) noexcept { // TODO: is there a better way? C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v_ >= 0); using U = typename std::conditional::type>::type; U v = (U) v_; // safe because we require v_ >= 0 unsigned __n = 1; const unsigned __b2 = 64u; const unsigned __b3 = __b2 * 8u; const unsigned long __b4 = __b3 * 8u; while(true) { if(v < 8u) return __n; if(v < __b2) return __n + 1; if(v < __b3) return __n + 2; if(v < __b4) return __n + 3; v /= (U) __b4; __n += 4; } } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @cond dev */ namespace detail { C4_INLINE_CONSTEXPR const char hexchars[] = "0123456789abcdef"; C4_INLINE_CONSTEXPR const char digits0099[] = "0001020304050607080910111213141516171819" "2021222324252627282930313233343536373839" "4041424344454647484950515253545556575859" "6061626364656667686970717273747576777879" "8081828384858687888990919293949596979899"; } // namespace detail /** @endcond */ C4_SUPPRESS_WARNING_GCC_PUSH C4_SUPPRESS_WARNING_GCC("-Warray-bounds") // gcc has false positives here #if (defined(__GNUC__) && (__GNUC__ >= 7)) C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has false positives here #endif /** @defgroup doc_write_unchecked Write with known number of digits * * Writes a value without checking the buffer length with regards to * the required number of digits to encode the value. It is the * responsibility of the caller to ensure that the provided number of * digits is enough to write the given value. Notwithstanding the * name, assertions are liberally performed, so this code is safe. * * @{ */ template C4_HOT C4_ALWAYS_INLINE void write_dec_unchecked(substr buf, T v, unsigned digits_v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); C4_ASSERT(buf.len >= digits_v); C4_XASSERT(digits_v == digits_dec(v)); // in bm_xtoa: checkoncelog_singlediv_write2 while(v >= T(100)) { T quo = v; quo /= T(100); const auto num = (v - quo * T(100)) << 1u; v = quo; buf.str[--digits_v] = detail::digits0099[num + 1]; buf.str[--digits_v] = detail::digits0099[num]; } if(v >= T(10)) { C4_ASSERT(digits_v == 2); const auto num = v << 1u; buf.str[1] = detail::digits0099[num + 1]; buf.str[0] = detail::digits0099[num]; } else { C4_ASSERT(digits_v == 1); buf.str[0] = (char)('0' + v); } } template C4_HOT C4_ALWAYS_INLINE void write_hex_unchecked(substr buf, T v, unsigned digits_v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); C4_ASSERT(buf.len >= digits_v); C4_XASSERT(digits_v == digits_hex(v)); do { buf.str[--digits_v] = detail::hexchars[v & T(15)]; v >>= 4; } while(v); C4_ASSERT(digits_v == 0); } template C4_HOT C4_ALWAYS_INLINE void write_oct_unchecked(substr buf, T v, unsigned digits_v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); C4_ASSERT(buf.len >= digits_v); C4_XASSERT(digits_v == digits_oct(v)); do { buf.str[--digits_v] = (char)('0' + (v & T(7))); v >>= 3; } while(v); C4_ASSERT(digits_v == 0); } template C4_HOT C4_ALWAYS_INLINE void write_bin_unchecked(substr buf, T v, unsigned digits_v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); C4_ASSERT(buf.len >= digits_v); C4_XASSERT(digits_v == digits_bin(v)); do { buf.str[--digits_v] = (char)('0' + (v & T(1))); v >>= 1; } while(v); C4_ASSERT(digits_v == 0); } /** @} */ // write_unchecked //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_write Write a value * * Writes a value without checking the buffer length * decimal number -- but asserting. * * @{ */ /** write an integer to a string in decimal format. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the required size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t write_dec(substr buf, T v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); unsigned digits = digits_dec(v); if(C4_LIKELY(buf.len >= digits)) write_dec_unchecked(buf, v, digits); return digits; } /** write an integer to a string in hexadecimal format. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note does not prefix with 0x * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the required size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t write_hex(substr buf, T v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); unsigned digits = digits_hex(v); if(C4_LIKELY(buf.len >= digits)) write_hex_unchecked(buf, v, digits); return digits; } /** write an integer to a string in octal format. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note does not prefix with 0o * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the required size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t write_oct(substr buf, T v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); unsigned digits = digits_oct(v); if(C4_LIKELY(buf.len >= digits)) write_oct_unchecked(buf, v, digits); return digits; } /** write an integer to a string in binary format. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note does not prefix with 0b * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the required size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t write_bin(substr buf, T v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(v >= 0); unsigned digits = digits_bin(v); C4_ASSERT(digits > 0); if(C4_LIKELY(buf.len >= digits)) write_bin_unchecked(buf, v, digits); return digits; } /** @cond dev */ namespace detail { template using NumberWriter = size_t (*)(substr, U); template writer> size_t write_num_digits(substr buf, T v, size_t num_digits) noexcept { C4_STATIC_ASSERT(std::is_integral::value); size_t ret = writer(buf, v); if(ret >= num_digits) return ret; else if(ret >= buf.len || num_digits > buf.len) return num_digits; C4_ASSERT(num_digits >= ret); size_t delta = static_cast(num_digits - ret); memmove(buf.str + delta, buf.str, ret); memset(buf.str, '0', delta); return num_digits; } } // namespace detail /** @endcond */ /** same as c4::write_dec(), but pad with zeroes on the left * such that the resulting string is @p num_digits wide. * If the given number is requires more than num_digits, then the number prevails. */ template C4_ALWAYS_INLINE size_t write_dec(substr buf, T val, size_t num_digits) noexcept { return detail::write_num_digits>(buf, val, num_digits); } /** same as c4::write_hex(), but pad with zeroes on the left * such that the resulting string is @p num_digits wide. * If the given number is requires more than num_digits, then the number prevails. */ template C4_ALWAYS_INLINE size_t write_hex(substr buf, T val, size_t num_digits) noexcept { return detail::write_num_digits>(buf, val, num_digits); } /** same as c4::write_bin(), but pad with zeroes on the left * such that the resulting string is @p num_digits wide. * If the given number is requires more than num_digits, then the number prevails. */ template C4_ALWAYS_INLINE size_t write_bin(substr buf, T val, size_t num_digits) noexcept { return detail::write_num_digits>(buf, val, num_digits); } /** same as c4::write_oct(), but pad with zeroes on the left * such that the resulting string is @p num_digits wide. * If the given number is requires more than num_digits, then the number prevails. */ template C4_ALWAYS_INLINE size_t write_oct(substr buf, T val, size_t num_digits) noexcept { return detail::write_num_digits>(buf, val, num_digits); } /** @} */ // write C4_SUPPRESS_WARNING_GCC_POP //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- C4_SUPPRESS_WARNING_MSVC_PUSH C4_SUPPRESS_WARNING_MSVC(4365) // '=': conversion from 'int' to 'I', signed/unsigned mismatch /** @defgroup doc_read Read a value * * @{ */ /** read a decimal integer from a string. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note The string must be trimmed. Whitespace is not accepted. * @note the string must not be empty * @note there is no check for overflow; the value wraps around * in a way similar to the standard C/C++ overflow behavior. * For example, `read_dec("128", &val)` returns true * and val will be set to 0 because 127 is the max i8 value. * @see overflows() to find out if a number string overflows a type range * @return true if the conversion was successful (no overflow check) */ template C4_NO_UBSAN_IOVRFLW C4_ALWAYS_INLINE bool read_dec(csubstr s, I *C4_RESTRICT v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(!s.empty()); *v = 0; for(char c : s) { if(C4_UNLIKELY(c < '0' || c > '9')) return false; *v = (*v) * I(10) + (I(c) - I('0')); } return true; } /** read an hexadecimal integer from a string. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note does not accept leading 0x or 0X * @note the string must not be empty * @note the string must be trimmed. Whitespace is not accepted. * @note there is no check for overflow; the value wraps around * in a way similar to the standard C/C++ overflow behavior. * For example, `read_hex("80", &val)` returns true * and val will be set to 0 because 7f is the max i8 value. * @see overflows() to find out if a number string overflows a type range * @return true if the conversion was successful (no overflow check) */ template C4_NO_UBSAN_IOVRFLW C4_ALWAYS_INLINE bool read_hex(csubstr s, I *C4_RESTRICT v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(!s.empty()); *v = 0; for(char c : s) { I cv; if(c >= '0' && c <= '9') cv = I(c) - I('0'); else if(c >= 'a' && c <= 'f') cv = I(10) + (I(c) - I('a')); else if(c >= 'A' && c <= 'F') cv = I(10) + (I(c) - I('A')); else return false; *v = (*v) * I(16) + cv; } return true; } /** read a binary integer from a string. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note does not accept leading 0b or 0B * @note the string must not be empty * @note the string must be trimmed. Whitespace is not accepted. * @note there is no check for overflow; the value wraps around * in a way similar to the standard C/C++ overflow behavior. * For example, `read_bin("10000000", &val)` returns true * and val will be set to 0 because 1111111 is the max i8 value. * @see overflows() to find out if a number string overflows a type range * @return true if the conversion was successful (no overflow check) */ template C4_NO_UBSAN_IOVRFLW C4_ALWAYS_INLINE bool read_bin(csubstr s, I *C4_RESTRICT v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(!s.empty()); *v = 0; for(char c : s) { *v <<= 1; if(c == '1') *v |= 1; else if(c != '0') return false; } return true; } /** read an octal integer from a string. This is the * lowest level (and the fastest) function to do this task. * @note does not accept negative numbers * @note does not accept leading 0o or 0O * @note the string must not be empty * @note the string must be trimmed. Whitespace is not accepted. * @note there is no check for overflow; the value wraps around * in a way similar to the standard C/C++ overflow behavior. * For example, `read_oct("200", &val)` returns true * and val will be set to 0 because 177 is the max i8 value. * @see overflows() to find out if a number string overflows a type range * @return true if the conversion was successful (no overflow check) */ template C4_NO_UBSAN_IOVRFLW C4_ALWAYS_INLINE bool read_oct(csubstr s, I *C4_RESTRICT v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_ASSERT(!s.empty()); *v = 0; for(char c : s) { if(C4_UNLIKELY(c < '0' || c > '7')) return false; *v = (*v) * I(8) + (I(c) - I('0')); } return true; } /** @} */ C4_SUPPRESS_WARNING_MSVC_POP //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wswitch-default") /** @cond dev */ namespace detail { inline size_t _itoa2buf(substr buf, size_t pos, csubstr val) noexcept { C4_ASSERT(pos + val.len <= buf.len); memcpy(buf.str + pos, val.str, val.len); return pos + val.len; } inline size_t _itoa2bufwithdigits(substr buf, size_t pos, size_t num_digits, csubstr val) noexcept { num_digits = num_digits > val.len ? num_digits - val.len : 0; C4_ASSERT(num_digits + val.len <= buf.len); for(size_t i = 0; i < num_digits; ++i) _c4append('0'); return detail::_itoa2buf(buf, pos, val); } template C4_NO_INLINE size_t _itoadec2buf(substr buf) noexcept { using digits_type = detail::charconv_digits; if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec)) return digits_type::maxdigits_dec; buf.str[0] = '-'; return detail::_itoa2buf(buf, 1, digits_type::min_value_dec()); } template C4_NO_INLINE size_t _itoa2buf(substr buf, I radix) noexcept { using digits_type = detail::charconv_digits; size_t pos = 0; if(C4_LIKELY(buf.len > 0)) buf.str[pos++] = '-'; switch(radix) { case I(10): if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec)) return digits_type::maxdigits_dec; pos =_itoa2buf(buf, pos, digits_type::min_value_dec()); break; case I(16): if(C4_UNLIKELY(buf.len < digits_type::maxdigits_hex)) return digits_type::maxdigits_hex; buf.str[pos++] = '0'; buf.str[pos++] = 'x'; pos = _itoa2buf(buf, pos, digits_type::min_value_hex()); break; case I( 2): if(C4_UNLIKELY(buf.len < digits_type::maxdigits_bin)) return digits_type::maxdigits_bin; buf.str[pos++] = '0'; buf.str[pos++] = 'b'; pos = _itoa2buf(buf, pos, digits_type::min_value_bin()); break; case I( 8): if(C4_UNLIKELY(buf.len < digits_type::maxdigits_oct)) return digits_type::maxdigits_oct; buf.str[pos++] = '0'; buf.str[pos++] = 'o'; pos = _itoa2buf(buf, pos, digits_type::min_value_oct()); break; } return pos; } template C4_NO_INLINE size_t _itoa2buf(substr buf, I radix, size_t num_digits) noexcept { using digits_type = detail::charconv_digits; size_t pos = 0; size_t needed_digits = 0; if(C4_LIKELY(buf.len > 0)) buf.str[pos++] = '-'; switch(radix) { case I(10): // add 1 to account for - needed_digits = num_digits+1 > digits_type::maxdigits_dec ? num_digits+1 : digits_type::maxdigits_dec; if(C4_UNLIKELY(buf.len < needed_digits)) return needed_digits; pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_dec()); break; case I(16): // add 3 to account for -0x needed_digits = num_digits+3 > digits_type::maxdigits_hex ? num_digits+3 : digits_type::maxdigits_hex; if(C4_UNLIKELY(buf.len < needed_digits)) return needed_digits; buf.str[pos++] = '0'; buf.str[pos++] = 'x'; pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_hex()); break; case I(2): // add 3 to account for -0b needed_digits = num_digits+3 > digits_type::maxdigits_bin ? num_digits+3 : digits_type::maxdigits_bin; if(C4_UNLIKELY(buf.len < needed_digits)) return needed_digits; C4_ASSERT(buf.len >= digits_type::maxdigits_bin); buf.str[pos++] = '0'; buf.str[pos++] = 'b'; pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_bin()); break; case I(8): // add 3 to account for -0o needed_digits = num_digits+3 > digits_type::maxdigits_oct ? num_digits+3 : digits_type::maxdigits_oct; if(C4_UNLIKELY(buf.len < needed_digits)) return needed_digits; C4_ASSERT(buf.len >= digits_type::maxdigits_oct); buf.str[pos++] = '0'; buf.str[pos++] = 'o'; pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_oct()); break; } return pos; } } // namespace detail /** @endcond */ /** @defgroup doc_itoa itoa: signed to chars * * @{ */ /** convert an integral signed decimal to a string. * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the needed size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t itoa(substr buf, T v) noexcept { C4_STATIC_ASSERT(std::is_signed::value); if(v >= T(0)) { // write_dec() checks the buffer size, so no need to check here return write_dec(buf, v); } // when T is the min value (eg i8: -128), negating it // will overflow, so treat the min as a special case else if(C4_LIKELY(v != std::numeric_limits::min())) { v = -v; unsigned digits = digits_dec(v); if(C4_LIKELY(buf.len >= digits + 1u)) { buf.str[0] = '-'; write_dec_unchecked(buf.sub(1), v, digits); } return digits + 1u; } return detail::_itoadec2buf(buf); } /** convert an integral signed integer to a string, using a specific * radix. The radix must be 2, 8, 10 or 16. * * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the needed size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix) noexcept { C4_STATIC_ASSERT(std::is_signed::value); C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16); C4_SUPPRESS_WARNING_GCC_PUSH #if (defined(__GNUC__) && (__GNUC__ >= 7)) C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has a false positive here #endif // when T is the min value (eg i8: -128), negating it // will overflow, so treat the min as a special case if(C4_LIKELY(v != std::numeric_limits::min())) { unsigned pos = 0; if(v < 0) { v = -v; if(C4_LIKELY(buf.len > 0)) buf.str[pos] = '-'; ++pos; } unsigned digits = 0; switch(radix) { case T(10): digits = digits_dec(v); if(C4_LIKELY(buf.len >= pos + digits)) write_dec_unchecked(buf.sub(pos), v, digits); break; case T(16): digits = digits_hex(v); if(C4_LIKELY(buf.len >= pos + 2u + digits)) { buf.str[pos + 0] = '0'; buf.str[pos + 1] = 'x'; write_hex_unchecked(buf.sub(pos + 2), v, digits); } digits += 2u; break; case T(2): digits = digits_bin(v); if(C4_LIKELY(buf.len >= pos + 2u + digits)) { buf.str[pos + 0] = '0'; buf.str[pos + 1] = 'b'; write_bin_unchecked(buf.sub(pos + 2), v, digits); } digits += 2u; break; case T(8): digits = digits_oct(v); if(C4_LIKELY(buf.len >= pos + 2u + digits)) { buf.str[pos + 0] = '0'; buf.str[pos + 1] = 'o'; write_oct_unchecked(buf.sub(pos + 2), v, digits); } digits += 2u; break; } return pos + digits; } C4_SUPPRESS_WARNING_GCC_POP // when T is the min value (eg i8: -128), negating it // will overflow return detail::_itoa2buf(buf, radix); } /** same as c4::itoa(), but pad with zeroes on the left such that the * resulting string is @p num_digits wide, not accounting for radix * prefix (0x,0o,0b). The @p radix must be 2, 8, 10 or 16. * * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the needed size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix, size_t num_digits) noexcept { C4_STATIC_ASSERT(std::is_signed::value); C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16); C4_SUPPRESS_WARNING_GCC_PUSH #if (defined(__GNUC__) && (__GNUC__ >= 7)) C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has a false positive here #endif // when T is the min value (eg i8: -128), negating it // will overflow, so treat the min as a special case if(C4_LIKELY(v != std::numeric_limits::min())) { unsigned pos = 0; if(v < 0) { v = -v; if(C4_LIKELY(buf.len > 0)) buf.str[pos] = '-'; ++pos; } unsigned total_digits = 0; switch(radix) { case T(10): total_digits = digits_dec(v); total_digits = pos + (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) write_dec(buf.sub(pos), v, num_digits); break; case T(16): total_digits = digits_hex(v); total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) { buf.str[pos + 0] = '0'; buf.str[pos + 1] = 'x'; write_hex(buf.sub(pos + 2), v, num_digits); } break; case T(2): total_digits = digits_bin(v); total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) { buf.str[pos + 0] = '0'; buf.str[pos + 1] = 'b'; write_bin(buf.sub(pos + 2), v, num_digits); } break; case T(8): total_digits = digits_oct(v); total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) { buf.str[pos + 0] = '0'; buf.str[pos + 1] = 'o'; write_oct(buf.sub(pos + 2), v, num_digits); } break; } return total_digits; } C4_SUPPRESS_WARNING_GCC_POP // when T is the min value (eg i8: -128), negating it // will overflow return detail::_itoa2buf(buf, radix, num_digits); } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_utoa utoa: unsigned to chars * * @{ */ /** convert an integral unsigned decimal to a string. * * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the needed size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t utoa(substr buf, T v) noexcept { C4_STATIC_ASSERT(std::is_unsigned::value); // write_dec() does the buffer length check, so no need to check here return write_dec(buf, v); } /** convert an integral unsigned integer to a string, using a specific * radix. The radix must be 2, 8, 10 or 16. * * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the needed size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix) noexcept { C4_STATIC_ASSERT(std::is_unsigned::value); C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8); unsigned digits = 0; switch(radix) { case T(10): digits = digits_dec(v); if(C4_LIKELY(buf.len >= digits)) write_dec_unchecked(buf, v, digits); break; case T(16): digits = digits_hex(v); if(C4_LIKELY(buf.len >= digits+2u)) { buf.str[0] = '0'; buf.str[1] = 'x'; write_hex_unchecked(buf.sub(2), v, digits); } digits += 2u; break; case T(2): digits = digits_bin(v); if(C4_LIKELY(buf.len >= digits+2u)) { buf.str[0] = '0'; buf.str[1] = 'b'; write_bin_unchecked(buf.sub(2), v, digits); } digits += 2u; break; case T(8): digits = digits_oct(v); if(C4_LIKELY(buf.len >= digits+2u)) { buf.str[0] = '0'; buf.str[1] = 'o'; write_oct_unchecked(buf.sub(2), v, digits); } digits += 2u; break; } return digits; } /** same as c4::utoa(), but pad with zeroes on the left such that the * resulting string is @p num_digits wide. The @p radix must be 2, * 8, 10 or 16. * * @note the resulting string is NOT zero-terminated. * @note it is ok to call this with an empty or too-small buffer; * no writes will occur, and the needed size will be returned * @return the number of characters required for the buffer. */ template C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix, size_t num_digits) noexcept { C4_STATIC_ASSERT(std::is_unsigned::value); C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8); unsigned total_digits = 0; switch(radix) { case T(10): total_digits = digits_dec(v); total_digits = (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) write_dec(buf, v, num_digits); break; case T(16): total_digits = digits_hex(v); total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) { buf.str[0] = '0'; buf.str[1] = 'x'; write_hex(buf.sub(2), v, num_digits); } break; case T(2): total_digits = digits_bin(v); total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) { buf.str[0] = '0'; buf.str[1] = 'b'; write_bin(buf.sub(2), v, num_digits); } break; case T(8): total_digits = digits_oct(v); total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits); if(C4_LIKELY(buf.len >= total_digits)) { buf.str[0] = '0'; buf.str[1] = 'o'; write_oct(buf.sub(2), v, num_digits); } break; } return total_digits; } C4_SUPPRESS_WARNING_GCC_POP /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_atoi atoi: chars to signed * * @{ */ /** Convert a trimmed string to a signed integral value. The input * string can be formatted as decimal, binary (prefix 0b or 0B), octal * (prefix 0o or 0O) or hexadecimal (prefix 0x or 0X). Strings with * leading zeroes are considered as decimal and not octal (unlike the * C/C++ convention). Every character in the input string is read for * the conversion; the input string must not contain any leading or * trailing whitespace. * * @return true if the conversion was successful. * * @note a positive sign is not accepted. ie, the string must not * start with '+' * * @note overflow is not detected: the return status is true even if * the conversion would return a value outside of the type's range, in * which case the result will wrap around the type's range. This is * similar to native behavior. See @ref doc_overflows and @ref * doc_overflow_checked for overflow checking utilities. * * @see atoi_first() if the string is not trimmed to the value to read. */ template C4_NO_UBSAN_IOVRFLW C4_ALWAYS_INLINE bool atoi(csubstr str, T * C4_RESTRICT v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); C4_STATIC_ASSERT(std::is_signed::value); if(C4_UNLIKELY(str.len == 0)) return false; C4_ASSERT(str.str[0] != '+'); T sign = 1; size_t start = 0; if(str.str[0] == '-') { if(C4_UNLIKELY(str.len == ++start)) return false; sign = -1; } bool parsed_ok = true; if(str.str[start] != '0') // this should be the common case, so put it first { parsed_ok = read_dec(str.sub(start), v); } else if(str.len > start + 1) { // starts with 0: is it 0x, 0o, 0b? const char pfx = str.str[start + 1]; if(pfx == 'x' || pfx == 'X') parsed_ok = str.len > start + 2 && read_hex(str.sub(start + 2), v); else if(pfx == 'b' || pfx == 'B') parsed_ok = str.len > start + 2 && read_bin(str.sub(start + 2), v); else if(pfx == 'o' || pfx == 'O') parsed_ok = str.len > start + 2 && read_oct(str.sub(start + 2), v); else parsed_ok = read_dec(str.sub(start + 1), v); } else { parsed_ok = read_dec(str.sub(start), v); } if(C4_LIKELY(parsed_ok)) *v *= sign; return parsed_ok; } /** Select the next range of characters in the string that can be parsed * as a signed integral value, and convert it using atoi(). Leading * whitespace (space, newline, tabs) is skipped. * @return the number of characters read for conversion, or csubstr::npos if the conversion failed * @see atoi() if the string is already trimmed to the value to read. * @see csubstr::first_int_span() */ template C4_ALWAYS_INLINE size_t atoi_first(csubstr str, T * C4_RESTRICT v) { csubstr trimmed = str.first_int_span(); if(trimmed.len == 0) return csubstr::npos; if(atoi(trimmed, v)) return static_cast(trimmed.end() - str.begin()); return csubstr::npos; } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @defgroup doc_atou atou: chars to unsigned * * @{ */ /** Convert a trimmed string to an unsigned integral value. The string can be * formatted as decimal, binary (prefix 0b or 0B), octal (prefix 0o or 0O) * or hexadecimal (prefix 0x or 0X). Every character in the input string is read * for the conversion; it must not contain any leading or trailing whitespace. * * @return true if the conversion was successful. * * @note overflow is not detected: the return status is true even if * the conversion would return a value outside of the type's range, in * which case the result will wrap around the type's range. See @ref * doc_overflows and @ref doc_overflow_checked for overflow checking * utilities. * * @note If the string has a minus character, the return status * will be false. * * @see atou_first() if the string is not trimmed to the value to read. */ template bool atou(csubstr str, T * C4_RESTRICT v) noexcept { C4_STATIC_ASSERT(std::is_integral::value); if(C4_UNLIKELY(str.len == 0 || str.front() == '-')) return false; bool parsed_ok = true; if(str.str[0] != '0') { parsed_ok = read_dec(str, v); } else { if(str.len > 1) { const char pfx = str.str[1]; if(pfx == 'x' || pfx == 'X') parsed_ok = str.len > 2 && read_hex(str.sub(2), v); else if(pfx == 'b' || pfx == 'B') parsed_ok = str.len > 2 && read_bin(str.sub(2), v); else if(pfx == 'o' || pfx == 'O') parsed_ok = str.len > 2 && read_oct(str.sub(2), v); else parsed_ok = read_dec(str, v); } else { *v = 0; // we know the first character is 0 } } return parsed_ok; } /** Select the next range of characters in the string that can be parsed * as an unsigned integral value, and convert it using atou(). Leading * whitespace (space, newline, tabs) is skipped. * @return the number of characters read for conversion, or csubstr::npos if the conversion faileds * @see atou() if the string is already trimmed to the value to read. * @see csubstr::first_uint_span() */ template C4_ALWAYS_INLINE size_t atou_first(csubstr str, T *v) { csubstr trimmed = str.first_uint_span(); if(trimmed.len == 0) return csubstr::npos; if(atou(trimmed, v)) return static_cast(trimmed.end() - str.begin()); return csubstr::npos; } /** @} */ #ifdef _MSC_VER # pragma warning(pop) #elif defined(__clang__) # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @cond dev */ namespace detail { inline bool check_overflow(csubstr str, csubstr limit) noexcept { if(str.len == limit.len) { for(size_t i = 0; i < limit.len; ++i) { if(str[i] < limit[i]) return false; else if(str[i] > limit[i]) return true; } return false; } else return str.len > limit.len; } } // namespace detail /** @endcond */ /** @defgroup doc_overflows overflows: does a number string overflow a type * * @{ */ /** Test if the following string would overflow when converted to * associated integral types; this function is dispatched with SFINAE * to handle differently signed and unsigned types. * @return true if number will overflow, false if it fits (or doesn't parse) * @see doc_overflow_checked for format specifiers to enforce no-overflow reads */ template auto overflows(csubstr str) noexcept -> typename std::enable_if::value, bool>::type { C4_STATIC_ASSERT(std::is_integral::value); if(C4_UNLIKELY(str.len == 0)) { return false; } else if(str.str[0] == '0') { if (str.len == 1) return false; switch (str.str[1]) { case 'x': case 'X': { size_t fno = str.first_not_of('0', 2); if (fno == csubstr::npos) return false; return !(str.len <= fno + (sizeof(T) * 2)); } case 'b': case 'B': { size_t fno = str.first_not_of('0', 2); if (fno == csubstr::npos) return false; return !(str.len <= fno +(sizeof(T) * 8)); } case 'o': case 'O': { size_t fno = str.first_not_of('0', 2); if(fno == csubstr::npos) return false; return detail::charconv_digits::is_oct_overflow(str.sub(fno)); } default: { size_t fno = str.first_not_of('0', 1); if(fno == csubstr::npos) return false; return detail::check_overflow(str.sub(fno), detail::charconv_digits::max_value_dec()); } } } else if(C4_UNLIKELY(str[0] == '-')) { return true; } else { return detail::check_overflow(str, detail::charconv_digits::max_value_dec()); } } /** Test if the following string would overflow when converted to * associated integral types; this function is dispatched with SFINAE * to handle differently signed and unsigned types. * * @return true if number will overflow, false if it fits (or doesn't parse) * @see doc_overflow_checked for format specifiers to enforce no-overflow reads */ template auto overflows(csubstr str) -> typename std::enable_if::value, bool>::type { C4_STATIC_ASSERT(std::is_integral::value); if(C4_UNLIKELY(str.len == 0)) return false; if(str.str[0] == '-') { if(str.str[1] == '0') { if(str.len == 2) return false; switch(str.str[2]) { case 'x': case 'X': { size_t fno = str.first_not_of('0', 3); if (fno == csubstr::npos) return false; return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_hex()); } case 'b': case 'B': { size_t fno = str.first_not_of('0', 3); if (fno == csubstr::npos) return false; return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_bin()); } case 'o': case 'O': { size_t fno = str.first_not_of('0', 3); if(fno == csubstr::npos) return false; return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_oct()); } default: { size_t fno = str.first_not_of('0', 2); if(fno == csubstr::npos) return false; return detail::check_overflow(str.sub(fno), detail::charconv_digits::min_value_dec()); } } } else return detail::check_overflow(str.sub(1), detail::charconv_digits::min_value_dec()); } else if(str.str[0] == '0') { if (str.len == 1) return false; switch(str.str[1]) { case 'x': case 'X': { size_t fno = str.first_not_of('0', 2); if (fno == csubstr::npos) return false; const size_t len = str.len - fno; return !((len < sizeof (T) * 2) || (len == sizeof(T) * 2 && str[fno] <= '7')); } case 'b': case 'B': { size_t fno = str.first_not_of('0', 2); if (fno == csubstr::npos) return false; return !(str.len <= fno + (sizeof(T) * 8 - 1)); } case 'o': case 'O': { size_t fno = str.first_not_of('0', 2); if(fno == csubstr::npos) return false; return detail::charconv_digits::is_oct_overflow(str.sub(fno)); } default: { size_t fno = str.first_not_of('0', 1); if(fno == csubstr::npos) return false; return detail::check_overflow(str.sub(fno), detail::charconv_digits::max_value_dec()); } } } else return detail::check_overflow(str, detail::charconv_digits::max_value_dec()); } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @cond dev */ namespace detail { #if (!C4CORE_HAVE_STD_FROMCHARS) /** @see http://www.exploringbinary.com/ for many good examples on float-str conversion */ template void get_real_format_str(char (& C4_RESTRICT fmt)[N], int precision, RealFormat_e formatting, const char* length_modifier="") { int iret; if(precision == -1) iret = snprintf(fmt, sizeof(fmt), "%%%s%c", length_modifier, formatting); else if(precision == 0) iret = snprintf(fmt, sizeof(fmt), "%%.%s%c", length_modifier, formatting); else iret = snprintf(fmt, sizeof(fmt), "%%.%d%s%c", precision, length_modifier, formatting); C4_ASSERT(iret >= 2 && size_t(iret) < sizeof(fmt)); C4_UNUSED(iret); } /** @todo we're depending on snprintf()/sscanf() for converting to/from * floating point numbers. Apparently, this increases the binary size * by a considerable amount. There are some lightweight printf * implementations: * * @see http://www.sparetimelabs.com/tinyprintf/tinyprintf.php (BSD) * @see https://github.com/weiss/c99-snprintf * @see https://github.com/nothings/stb/blob/master/stb_sprintf.h * @see http://www.exploringbinary.com/ * @see https://blog.benoitblanchon.fr/lightweight-float-to-string/ * @see http://www.ryanjuckett.com/programming/printing-floating-point-numbers/ */ template size_t print_one(substr str, const char* full_fmt, T v) { #ifdef _MSC_VER /** use _snprintf() to prevent early termination of the output * for writing the null character at the last position * @see https://msdn.microsoft.com/en-us/library/2ts7cx93.aspx */ int iret = _snprintf(str.str, str.len, full_fmt, v); if(iret < 0) { /* when buf.len is not enough, VS returns a negative value. * so call it again with a negative value for getting an * actual length of the string */ iret = snprintf(nullptr, 0, full_fmt, v); C4_ASSERT(iret > 0); } size_t ret = (size_t) iret; return ret; #else int iret = snprintf(str.str, str.len, full_fmt, v); C4_ASSERT(iret >= 0); size_t ret = (size_t) iret; if(ret >= str.len) ++ret; /* snprintf() reserves the last character to write \0 */ return ret; #endif } #endif // (!C4CORE_HAVE_STD_FROMCHARS) #if (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT) /** scans a string using the given type format, while at the same time * allowing non-null-terminated strings AND guaranteeing that the given * string length is strictly respected, so that no buffer overflows * might occur. */ template inline size_t scan_one(csubstr str, const char *type_fmt, T *v) { /* snscanf() is absolutely needed here as we must be sure that * str.len is strictly respected, because substr is * generally not null-terminated. * * Alas, there is no snscanf(). * * So we fake it by using a dynamic format with an explicit * field size set to the length of the given span. * This trick is taken from: * https://stackoverflow.com/a/18368910/5875572 */ /* this is the actual format we'll use for scanning */ char fmt[16]; /* write the length into it. Eg "%12f". * Also, get the number of characters read from the string. * So the final format ends up as "%12f%n"*/ int iret = std::snprintf(fmt, sizeof(fmt), "%%" "%zu" "%s" "%%n", str.len, type_fmt); /* no nasty surprises, please! */ C4_ASSERT(iret >= 0 && size_t(iret) < C4_COUNTOF(fmt)); /* now we scan with confidence that the span length is respected */ int num_chars; iret = std::sscanf(str.str, fmt, v, &num_chars); /* scanf returns the number of successful conversions */ if(iret != 1) return csubstr::npos; C4_ASSERT(num_chars >= 0); return (size_t)(num_chars); } #endif // (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT) #if C4CORE_HAVE_STD_TOCHARS template C4_ALWAYS_INLINE size_t rtoa(substr buf, T v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept { std::to_chars_result result; size_t pos = 0; if(formatting == FTOA_HEXA) { if(buf.len > size_t(2)) { buf.str[0] = '0'; buf.str[1] = 'x'; } pos += size_t(2); } if(precision == -1) result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting); else result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting, precision); if(result.ec == std::errc()) { // all good, no errors. C4_ASSERT(result.ptr >= buf.str); ptrdiff_t delta = result.ptr - buf.str; return static_cast(delta); } C4_ASSERT(result.ec == std::errc::value_too_large); // This is unfortunate. // // When the result can't fit in the given buffer, // std::to_chars() returns the end pointer it was originally // given, which is useless because here we would like to know // _exactly_ how many characters the buffer must have to fit // the result. // // So we take the pessimistic view, and assume as many digits // as could ever be required: size_t ret = static_cast(std::numeric_limits::max_digits10); return ret > buf.len ? ret : buf.len + 1; } #endif // C4CORE_HAVE_STD_TOCHARS #if C4CORE_HAVE_FAST_FLOAT template C4_ALWAYS_INLINE bool scan_rhex(csubstr s, T *C4_RESTRICT val) noexcept { C4_ASSERT(s.len > 0); C4_ASSERT(s.str[0] != '-'); C4_ASSERT(s.str[0] != '+'); C4_ASSERT(!s.begins_with("0x")); C4_ASSERT(!s.begins_with("0X")); size_t pos = 0; // integer part for( ; pos < s.len; ++pos) { const char c = s.str[pos]; if(c >= '0' && c <= '9') *val = *val * T(16) + T(c - '0'); else if(c >= 'a' && c <= 'f') *val = *val * T(16) + T(c - 'a'); else if(c >= 'A' && c <= 'F') *val = *val * T(16) + T(c - 'A'); else if(c == '.') { ++pos; break; // follow on to mantissa } else if(c == 'p' || c == 'P') { ++pos; goto power; // no mantissa given, jump to power } else { return false; } } // mantissa { // 0.0625 == 1/16 == value of first digit after the comma for(T digit = T(0.0625); pos < s.len; ++pos, digit /= T(16)) { const char c = s.str[pos]; if(c >= '0' && c <= '9') *val += digit * T(c - '0'); else if(c >= 'a' && c <= 'f') *val += digit * T(c - 'a'); else if(c >= 'A' && c <= 'F') *val += digit * T(c - 'A'); else if(c == 'p' || c == 'P') { ++pos; goto power; // mantissa finished, jump to power } else { return false; } } } return true; power: if(C4_LIKELY(pos < s.len)) { if(s.str[pos] == '+') // atoi() cannot handle a leading '+' ++pos; if(C4_LIKELY(pos < s.len)) { int16_t powval = {}; if(C4_LIKELY(atoi(s.sub(pos), &powval))) { *val *= ipow(powval); return true; } } } return false; } #endif } // namespace detail /** @endcond */ #undef _c4appendhex #undef _c4append /** @defgroup doc_ftoa ftoa: float32 to chars * * @{ */ /** Convert a single-precision real number to string. The string will * in general be NOT null-terminated. For FTOA_FLEX, \p precision is * the number of significand digits. Otherwise \p precision is the * number of decimals. It is safe to call this function with an empty * or too-small buffer. * * @return the size of the buffer needed to write the number */ C4_ALWAYS_INLINE size_t ftoa(substr str, float v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept { #if C4CORE_HAVE_STD_TOCHARS return detail::rtoa(str, v, precision, formatting); #else char fmt[16]; detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/""); return detail::print_one(str, fmt, v); #endif } /** @} */ /** @defgroup doc_dtoa dtoa: float64 to chars * * @{ */ /** Convert a double-precision real number to string. The string will * in general be NOT null-terminated. For FTOA_FLEX, \p precision is * the number of significand digits. Otherwise \p precision is the * number of decimals. It is safe to call this function with an empty * or too-small buffer. * * @return the size of the buffer needed to write the number */ C4_ALWAYS_INLINE size_t dtoa(substr str, double v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept { #if C4CORE_HAVE_STD_TOCHARS return detail::rtoa(str, v, precision, formatting); #else char fmt[16]; detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/"l"); return detail::print_one(str, fmt, v); #endif } /** @} */ /** @defgroup doc_atof atof: chars to float32 * * @{ */ /** Convert a string to a single precision real number. * The input string must be trimmed to the value, ie * no leading or trailing whitespace can be present. * @return true iff the conversion succeeded * @see atof_first() if the string is not trimmed */ C4_ALWAYS_INLINE bool atof(csubstr str, float * C4_RESTRICT v) noexcept { C4_ASSERT(str.len > 0); C4_ASSERT(str.triml(" \r\t\n").len == str.len); #if C4CORE_HAVE_FAST_FLOAT // fastfloat cannot parse hexadecimal floats bool isneg = (str.str[0] == '-'); csubstr rem = str.sub(isneg || str.str[0] == '+'); if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) { fast_float::from_chars_result result; result = fast_float::from_chars(str.str, str.str + str.len, *v); return result.ec == std::errc(); } else if(detail::scan_rhex(rem.sub(2), v)) { *v *= isneg ? -1.f : 1.f; return true; } return false; #elif C4CORE_HAVE_STD_FROMCHARS std::from_chars_result result; result = std::from_chars(str.str, str.str + str.len, *v); return result.ec == std::errc(); #else csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+'); if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) return detail::scan_one(str, "f", v) != csubstr::npos; else return detail::scan_one(str, "a", v) != csubstr::npos; #endif } /** Convert a string to a single precision real number. * Leading whitespace is skipped until valid characters are found. * @return the number of characters read from the string, or npos if * conversion was not successful or if the string was empty */ inline size_t atof_first(csubstr str, float * C4_RESTRICT v) noexcept { csubstr trimmed = str.first_real_span(); if(trimmed.len == 0) return csubstr::npos; if(atof(trimmed, v)) return static_cast(trimmed.end() - str.begin()); return csubstr::npos; } /** @} */ /** @defgroup doc_atod atod: chars to float64 * * @{ */ /** Convert a string to a double precision real number. * The input string must be trimmed to the value, ie * no leading or trailing whitespace can be present. * @return true iff the conversion succeeded * @see atod_first() if the string is not trimmed */ C4_ALWAYS_INLINE bool atod(csubstr str, double * C4_RESTRICT v) noexcept { C4_ASSERT(str.triml(" \r\t\n").len == str.len); #if C4CORE_HAVE_FAST_FLOAT // fastfloat cannot parse hexadecimal floats bool isneg = (str.str[0] == '-'); csubstr rem = str.sub(isneg || str.str[0] == '+'); if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) { fast_float::from_chars_result result; result = fast_float::from_chars(str.str, str.str + str.len, *v); return result.ec == std::errc(); } else if(detail::scan_rhex(rem.sub(2), v)) { *v *= isneg ? -1. : 1.; return true; } return false; #elif C4CORE_HAVE_STD_FROMCHARS std::from_chars_result result; result = std::from_chars(str.str, str.str + str.len, *v); return result.ec == std::errc(); #else csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+'); if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X')))) return detail::scan_one(str, "lf", v) != csubstr::npos; else return detail::scan_one(str, "la", v) != csubstr::npos; #endif } /** Convert a string to a double precision real number. * Leading whitespace is skipped until valid characters are found. * @return the number of characters read from the string, or npos if * conversion was not successful or if the string was empty */ inline size_t atod_first(csubstr str, double * C4_RESTRICT v) noexcept { csubstr trimmed = str.first_real_span(); if(trimmed.len == 0) return csubstr::npos; if(atod(trimmed, v)) return static_cast(trimmed.end() - str.begin()); return csubstr::npos; } /** @} */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // generic versions /** @cond dev */ // on some platforms, (unsigned) int and (unsigned) long // are not any of the fixed length types above #define _C4_IF_NOT_FIXED_LENGTH_I(T, ty) C4_ALWAYS_INLINE typename std::enable_if::value && !is_fixed_length::value_i, ty> #define _C4_IF_NOT_FIXED_LENGTH_U(T, ty) C4_ALWAYS_INLINE typename std::enable_if::value && !is_fixed_length::value_u, ty> /** @endcond*/ /** @defgroup doc_xtoa xtoa: generic value to chars * * Dispatches to the most appropriate and efficient conversion * function * * @{ */ C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v) noexcept { return write_dec(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v) noexcept { return write_dec(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v) noexcept { return write_dec(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v) noexcept { return write_dec(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v) noexcept { return itoa(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v) noexcept { return itoa(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v) noexcept { return itoa(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v) noexcept { return itoa(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, float v) noexcept { return ftoa(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, double v) noexcept { return dtoa(s, v); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v, uint8_t radix) noexcept { return utoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v, uint16_t radix) noexcept { return utoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v, uint32_t radix) noexcept { return utoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v, uint64_t radix) noexcept { return utoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v, int8_t radix) noexcept { return itoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v, int16_t radix) noexcept { return itoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v, int32_t radix) noexcept { return itoa(s, v, radix); } C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v, int64_t radix) noexcept { return itoa(s, v, radix); } C4_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); } C4_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); } C4_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); } C4_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); } C4_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); } C4_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); } C4_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); } C4_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); } C4_ALWAYS_INLINE size_t xtoa(substr s, float v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return ftoa(s, v, precision, formatting); } C4_ALWAYS_INLINE size_t xtoa(substr s, double v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return dtoa(s, v, precision, formatting); } template _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type xtoa(substr buf, T v) noexcept { return itoa(buf, v); } template _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type xtoa(substr buf, T v) noexcept { return write_dec(buf, v); } template C4_ALWAYS_INLINE size_t xtoa(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); } /** @} */ /** @defgroup doc_atox atox: generic chars to value * * Dispatches to the most appropriate and efficient conversion * function * * @{ */ C4_ALWAYS_INLINE bool atox(csubstr s, uint8_t *C4_RESTRICT v) noexcept { return atou(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, uint16_t *C4_RESTRICT v) noexcept { return atou(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, uint32_t *C4_RESTRICT v) noexcept { return atou(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, uint64_t *C4_RESTRICT v) noexcept { return atou(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, int8_t *C4_RESTRICT v) noexcept { return atoi(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, int16_t *C4_RESTRICT v) noexcept { return atoi(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, int32_t *C4_RESTRICT v) noexcept { return atoi(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, int64_t *C4_RESTRICT v) noexcept { return atoi(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, float *C4_RESTRICT v) noexcept { return atof(s, v); } C4_ALWAYS_INLINE bool atox(csubstr s, double *C4_RESTRICT v) noexcept { return atod(s, v); } template _C4_IF_NOT_FIXED_LENGTH_I(T, bool )::type atox(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi(buf, v); } template _C4_IF_NOT_FIXED_LENGTH_U(T, bool )::type atox(csubstr buf, T *C4_RESTRICT v) noexcept { return atou(buf, v); } template 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; } /** @} */ /** @defgroup doc_to_chars to_chars: generalized chars to value * * Convert the given value, writing into the string. The resulting * string will NOT be null-terminated. Return the number of * characters needed. This function is safe to call when the string * is too small - no writes will occur beyond the string's last * character. * * Dispatches to the most appropriate and efficient conversion * function. * * @see write_dec, doc_utoa, doc_itoa, doc_ftoa, doc_dtoa * * @warning When serializing floating point values (float or double), * be aware that because it uses defaults, to_chars() may cause a * truncation of the precision. To enforce a particular precision, use * for example @ref c4::fmt::real, or call directly @ref c4::ftoa or * @ref c4::dtoa. * * @{ */ C4_ALWAYS_INLINE size_t to_chars(substr buf, uint8_t v) noexcept { return write_dec(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, uint16_t v) noexcept { return write_dec(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, uint32_t v) noexcept { return write_dec(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, uint64_t v) noexcept { return write_dec(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, int8_t v) noexcept { return itoa(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, int16_t v) noexcept { return itoa(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, int32_t v) noexcept { return itoa(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, int64_t v) noexcept { return itoa(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, float v) noexcept { return ftoa(buf, v); } C4_ALWAYS_INLINE size_t to_chars(substr buf, double v) noexcept { return dtoa(buf, v); } template _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type to_chars(substr buf, T v) noexcept { return itoa(buf, v); } template _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type to_chars(substr buf, T v) noexcept { return write_dec(buf, v); } template C4_ALWAYS_INLINE size_t to_chars(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); } /** @} */ /** @defgroup doc_from_chars from_chars: generalized chars to value * * Read a value from the string, which must be trimmed to the value * (ie, no leading/trailing whitespace). return true if the * conversion succeeded. There is no check for overflow; the value * wraps around in a way similar to the standard C/C++ overflow * behavior. For example, from_chars("128", &val) returns true * and val will be set tot 0. See @ref doc_overflows and @ref * doc_overflow_checked for facilities enforcing no-overflow. * * Dispatches to the most appropriate and efficient conversion * function * * @see doc_from_chars_first, atou, atoi, atof, atod * @{ */ C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint8_t *C4_RESTRICT v) noexcept { return atou(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, int8_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, int16_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, int32_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, int64_t *C4_RESTRICT v) noexcept { return atoi(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, float *C4_RESTRICT v) noexcept { return atof(buf, v); } C4_ALWAYS_INLINE bool from_chars(csubstr buf, double *C4_RESTRICT v) noexcept { return atod(buf, v); } template _C4_IF_NOT_FIXED_LENGTH_I(T, bool )::type from_chars(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi(buf, v); } template _C4_IF_NOT_FIXED_LENGTH_U(T, bool )::type from_chars(csubstr buf, T *C4_RESTRICT v) noexcept { return atou(buf, v); } template 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; } /** @defgroup doc_from_chars_first from_chars_first: generalized chars to value * * Read the first valid sequence of characters from the string, * skipping leading whitespace, and convert it using @ref doc_from_chars . * Return the number of characters read for converting. * * Dispatches to the most appropriate and efficient conversion * function. * * @see atou_first, atoi_first, atof_first, atod_first * @{ */ C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint8_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int8_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int16_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int32_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int64_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, float *C4_RESTRICT v) noexcept { return atof_first(buf, v); } C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, double *C4_RESTRICT v) noexcept { return atod_first(buf, v); } template _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); } template _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); } template 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; } /** @} */ /** @} */ #undef _C4_IF_NOT_FIXED_LENGTH_I #undef _C4_IF_NOT_FIXED_LENGTH_U //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** call to_chars() and return a substr consisting of the * written portion of the input buffer. Ie, same as to_chars(), * but return a substr instead of a size_t. * Convert the given value to a string using to_chars(), and * return the resulting string, up to and including the last * written character. * @ingroup doc_to_chars * @see to_chars() */ template C4_ALWAYS_INLINE substr to_chars_sub(substr buf, T const& C4_RESTRICT v) noexcept { size_t sz = to_chars(buf, v); return buf.left_of(sz <= buf.len ? sz : buf.len); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // bool implementation /** @ingroup doc_to_chars */ C4_ALWAYS_INLINE size_t to_chars(substr buf, bool v) noexcept { int val = v; return to_chars(buf, val); } /** @ingroup doc_from_chars */ inline bool from_chars(csubstr buf, bool * C4_RESTRICT v) noexcept { if(buf == '0') { *v = false; return true; } else if(buf == '1') { *v = true; return true; } else if(buf == "false") { *v = false; return true; } else if(buf == "true") { *v = true; return true; } else if(buf == "False") { *v = false; return true; } else if(buf == "True") { *v = true; return true; } else if(buf == "FALSE") { *v = false; return true; } else if(buf == "TRUE") { *v = true; return true; } // fallback to c-style int bools int val = 0; bool ret = from_chars(buf, &val); if(C4_LIKELY(ret)) { *v = (val != 0); } return ret; } /** @ingroup doc_from_chars_first */ inline size_t from_chars_first(csubstr buf, bool * C4_RESTRICT v) noexcept { csubstr trimmed = buf.first_non_empty_span(); if(trimmed.len == 0 || !from_chars(buf, v)) return csubstr::npos; return trimmed.len; } //----------------------------------------------------------------------------- // single-char implementation /** @ingroup doc_to_chars */ inline size_t to_chars(substr buf, char v) noexcept { if(buf.len > 0) { C4_XASSERT(buf.str); buf.str[0] = v; } return 1; } /** extract a single character from a substring * @note to extract a string instead and not just a single character, use the csubstr overload * @ingroup doc_from_chars * */ inline bool from_chars(csubstr buf, char * C4_RESTRICT v) noexcept { if(buf.len != 1) return false; C4_XASSERT(buf.str); *v = buf.str[0]; return true; } /** @ingroup doc_from_chars_first */ inline size_t from_chars_first(csubstr buf, char * C4_RESTRICT v) noexcept { if(buf.len < 1) return csubstr::npos; *v = buf.str[0]; return 1; } //----------------------------------------------------------------------------- // csubstr implementation /** @ingroup doc_to_chars */ inline size_t to_chars(substr buf, csubstr v) noexcept { C4_ASSERT(!buf.overlaps(v)); size_t len = buf.len < v.len ? buf.len : v.len; // calling memcpy with null strings is undefined behavior // and will wreak havoc in calling code's branches. // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 if(len) { C4_ASSERT(buf.str != nullptr); C4_ASSERT(v.str != nullptr); memcpy(buf.str, v.str, len); } return v.len; } /** @ingroup doc_from_chars */ inline bool from_chars(csubstr buf, csubstr *C4_RESTRICT v) noexcept { *v = buf; return true; } /** @ingroup doc_from_chars_first */ inline size_t from_chars_first(substr buf, csubstr * C4_RESTRICT v) noexcept { csubstr trimmed = buf.first_non_empty_span(); if(trimmed.len == 0) return csubstr::npos; *v = trimmed; return static_cast(trimmed.end() - buf.begin()); } //----------------------------------------------------------------------------- // substr /** @ingroup doc_to_chars */ inline size_t to_chars(substr buf, substr v) noexcept { C4_ASSERT(!buf.overlaps(v)); size_t len = buf.len < v.len ? buf.len : v.len; // calling memcpy with null strings is undefined behavior // and will wreak havoc in calling code's branches. // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 if(len) { C4_ASSERT(buf.str != nullptr); C4_ASSERT(v.str != nullptr); memcpy(buf.str, v.str, len); } return v.len; } /** @ingroup doc_from_chars */ inline bool from_chars(csubstr buf, substr * C4_RESTRICT v) noexcept { C4_ASSERT(!buf.overlaps(*v)); // is the destination buffer wide enough? if(v->len >= buf.len) { // calling memcpy with null strings is undefined behavior // and will wreak havoc in calling code's branches. // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 if(buf.len) { C4_ASSERT(buf.str != nullptr); C4_ASSERT(v->str != nullptr); memcpy(v->str, buf.str, buf.len); } v->len = buf.len; return true; } return false; } /** @ingroup doc_from_chars_first */ inline size_t from_chars_first(csubstr buf, substr * C4_RESTRICT v) noexcept { csubstr trimmed = buf.first_non_empty_span(); C4_ASSERT(!trimmed.overlaps(*v)); if(C4_UNLIKELY(trimmed.len == 0)) return csubstr::npos; size_t len = trimmed.len > v->len ? v->len : trimmed.len; // calling memcpy with null strings is undefined behavior // and will wreak havoc in calling code's branches. // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637 if(len) { C4_ASSERT(buf.str != nullptr); C4_ASSERT(v->str != nullptr); memcpy(v->str, trimmed.str, len); } if(C4_UNLIKELY(trimmed.len > v->len)) return csubstr::npos; return static_cast(trimmed.end() - buf.begin()); } //----------------------------------------------------------------------------- /** @ingroup doc_to_chars */ template inline size_t to_chars(substr buf, const char (& C4_RESTRICT v)[N]) noexcept { csubstr sp(v); return to_chars(buf, sp); } /** @ingroup doc_to_chars */ inline size_t to_chars(substr buf, const char * C4_RESTRICT v) noexcept { return to_chars(buf, to_csubstr(v)); } /** @} */ } // namespace c4 #ifdef _MSC_VER # pragma warning(pop) #endif #if defined(__clang__) # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif #endif /* _C4_CHARCONV_HPP_ */