Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/dep/rapidyaml/include/c4/charconv.hpp
4261 views
1
#ifndef _C4_CHARCONV_HPP_
2
#define _C4_CHARCONV_HPP_
3
4
/** @file charconv.hpp Lightweight generic type-safe wrappers for
5
* converting individual values to/from strings.
6
*
7
* These are the main functions:
8
*
9
* @code{.cpp}
10
* // Convert the given value, writing into the string.
11
* // The resulting string will NOT be null-terminated.
12
* // Return the number of characters needed.
13
* // This function is safe to call when the string is too small -
14
* // no writes will occur beyond the string's last character.
15
* template<class T> size_t c4::to_chars(substr buf, T const& C4_RESTRICT val);
16
*
17
*
18
* // Convert the given value to a string using to_chars(), and
19
* // return the resulting string, up to and including the last
20
* // written character.
21
* template<class T> substr c4::to_chars_sub(substr buf, T const& C4_RESTRICT val);
22
*
23
*
24
* // Read a value from the string, which must be
25
* // trimmed to the value (ie, no leading/trailing whitespace).
26
* // return true if the conversion succeeded.
27
* // There is no check for overflow; the value wraps around in a way similar
28
* // to the standard C/C++ overflow behavior. For example,
29
* // from_chars<int8_t>("128", &val) returns true and val will be
30
* // set tot 0.
31
* template<class T> bool c4::from_chars(csubstr buf, T * C4_RESTRICT val);
32
*
33
*
34
* // Read the first valid sequence of characters from the string,
35
* // skipping leading whitespace, and convert it using from_chars().
36
* // Return the number of characters read for converting.
37
* template<class T> size_t c4::from_chars_first(csubstr buf, T * C4_RESTRICT val);
38
* @endcode
39
*/
40
41
#include "c4/language.hpp"
42
#include <inttypes.h>
43
#include <type_traits>
44
#include <climits>
45
#include <limits>
46
#include <utility>
47
48
#include "c4/config.hpp"
49
#include "c4/substr.hpp"
50
#include "c4/std/std_fwd.hpp"
51
#include "c4/memory_util.hpp"
52
#include "c4/szconv.hpp"
53
54
#ifndef C4CORE_NO_FAST_FLOAT
55
# if (C4_CPP >= 17)
56
# if defined(_MSC_VER)
57
# if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros
58
# include <charconv>
59
# define C4CORE_HAVE_STD_TOCHARS 1
60
# define C4CORE_HAVE_STD_FROMCHARS 0 // prefer fast_float with MSVC
61
# define C4CORE_HAVE_FAST_FLOAT 1
62
# else
63
# define C4CORE_HAVE_STD_TOCHARS 0
64
# define C4CORE_HAVE_STD_FROMCHARS 0
65
# define C4CORE_HAVE_FAST_FLOAT 1
66
# endif
67
# else
68
# if __has_include(<charconv>)
69
# include <charconv>
70
# if defined(__cpp_lib_to_chars)
71
# define C4CORE_HAVE_STD_TOCHARS 1
72
# define C4CORE_HAVE_STD_FROMCHARS 0 // glibc uses fast_float internally
73
# define C4CORE_HAVE_FAST_FLOAT 1
74
# else
75
# define C4CORE_HAVE_STD_TOCHARS 0
76
# define C4CORE_HAVE_STD_FROMCHARS 0
77
# define C4CORE_HAVE_FAST_FLOAT 1
78
# endif
79
# else
80
# define C4CORE_HAVE_STD_TOCHARS 0
81
# define C4CORE_HAVE_STD_FROMCHARS 0
82
# define C4CORE_HAVE_FAST_FLOAT 1
83
# endif
84
# endif
85
# else
86
# define C4CORE_HAVE_STD_TOCHARS 0
87
# define C4CORE_HAVE_STD_FROMCHARS 0
88
# define C4CORE_HAVE_FAST_FLOAT 1
89
# endif
90
# if C4CORE_HAVE_FAST_FLOAT
91
C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wsign-conversion")
92
C4_SUPPRESS_WARNING_GCC("-Warray-bounds")
93
# if defined(__GNUC__) && __GNUC__ >= 5
94
C4_SUPPRESS_WARNING_GCC("-Wshift-count-overflow")
95
# endif
96
//# include "c4/ext/fast_float.hpp"
97
#include "fast_float/fast_float.h"
98
C4_SUPPRESS_WARNING_GCC_POP
99
# endif
100
#elif (C4_CPP >= 17)
101
# define C4CORE_HAVE_FAST_FLOAT 0
102
# if defined(_MSC_VER)
103
# if (C4_MSVC_VERSION >= C4_MSVC_VERSION_2019) // VS2017 and lower do not have these macros
104
# include <charconv>
105
# define C4CORE_HAVE_STD_TOCHARS 1
106
# define C4CORE_HAVE_STD_FROMCHARS 1
107
# else
108
# define C4CORE_HAVE_STD_TOCHARS 0
109
# define C4CORE_HAVE_STD_FROMCHARS 0
110
# endif
111
# else
112
# if __has_include(<charconv>)
113
# include <charconv>
114
# if defined(__cpp_lib_to_chars)
115
# define C4CORE_HAVE_STD_TOCHARS 1
116
# define C4CORE_HAVE_STD_FROMCHARS 1 // glibc uses fast_float internally
117
# else
118
# define C4CORE_HAVE_STD_TOCHARS 0
119
# define C4CORE_HAVE_STD_FROMCHARS 0
120
# endif
121
# else
122
# define C4CORE_HAVE_STD_TOCHARS 0
123
# define C4CORE_HAVE_STD_FROMCHARS 0
124
# endif
125
# endif
126
#else
127
# define C4CORE_HAVE_STD_TOCHARS 0
128
# define C4CORE_HAVE_STD_FROMCHARS 0
129
# define C4CORE_HAVE_FAST_FLOAT 0
130
#endif
131
132
133
#if !C4CORE_HAVE_STD_FROMCHARS
134
#include <cstdio>
135
#endif
136
137
138
#if defined(_MSC_VER)
139
# pragma warning(push)
140
# pragma warning(disable: 4996) // snprintf/scanf: this function or variable may be unsafe
141
# if C4_MSVC_VERSION != C4_MSVC_VERSION_2017
142
# pragma warning(disable: 4800) //'int': forcing value to bool 'true' or 'false' (performance warning)
143
# endif
144
#endif
145
146
#if defined(__clang__)
147
# pragma clang diagnostic push
148
# pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare"
149
# pragma clang diagnostic ignored "-Wformat-nonliteral"
150
# pragma clang diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision
151
# pragma clang diagnostic ignored "-Wold-style-cast"
152
#elif defined(__GNUC__)
153
# pragma GCC diagnostic push
154
# pragma GCC diagnostic ignored "-Wformat-nonliteral"
155
# pragma GCC diagnostic ignored "-Wdouble-promotion" // implicit conversion increases floating-point precision
156
# pragma GCC diagnostic ignored "-Wuseless-cast"
157
# pragma GCC diagnostic ignored "-Wold-style-cast"
158
#endif
159
160
161
namespace c4 {
162
163
#if C4CORE_HAVE_STD_TOCHARS
164
/** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */
165
typedef enum : std::underlying_type<std::chars_format>::type {
166
/** print the real number in floating point format (like %f) */
167
FTOA_FLOAT = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::fixed),
168
/** print the real number in scientific format (like %e) */
169
FTOA_SCIENT = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::scientific),
170
/** print the real number in flexible format (like %g) */
171
FTOA_FLEX = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::general),
172
/** print the real number in hexadecimal format (like %a) */
173
FTOA_HEXA = static_cast<std::underlying_type<std::chars_format>::type>(std::chars_format::hex),
174
} RealFormat_e;
175
#else
176
/** @warning Use only the symbol. Do not rely on the type or naked value of this enum. */
177
typedef enum : char {
178
/** print the real number in floating point format (like %f) */
179
FTOA_FLOAT = 'f',
180
/** print the real number in scientific format (like %e) */
181
FTOA_SCIENT = 'e',
182
/** print the real number in flexible format (like %g) */
183
FTOA_FLEX = 'g',
184
/** print the real number in hexadecimal format (like %a) */
185
FTOA_HEXA = 'a',
186
} RealFormat_e;
187
#endif
188
189
190
/** in some platforms, int,unsigned int
191
* are not any of int8_t...int64_t and
192
* long,unsigned long are not any of uint8_t...uint64_t */
193
template<class T>
194
struct is_fixed_length
195
{
196
enum : bool {
197
/** true if T is one of the fixed length signed types */
198
value_i = (std::is_integral<T>::value
199
&& (std::is_same<T, int8_t>::value
200
|| std::is_same<T, int16_t>::value
201
|| std::is_same<T, int32_t>::value
202
|| std::is_same<T, int64_t>::value)),
203
/** true if T is one of the fixed length unsigned types */
204
value_u = (std::is_integral<T>::value
205
&& (std::is_same<T, uint8_t>::value
206
|| std::is_same<T, uint16_t>::value
207
|| std::is_same<T, uint32_t>::value
208
|| std::is_same<T, uint64_t>::value)),
209
/** true if T is one of the fixed length signed or unsigned types */
210
value = value_i || value_u
211
};
212
};
213
214
215
//-----------------------------------------------------------------------------
216
//-----------------------------------------------------------------------------
217
//-----------------------------------------------------------------------------
218
219
#ifdef _MSC_VER
220
# pragma warning(push)
221
#elif defined(__clang__)
222
# pragma clang diagnostic push
223
#elif defined(__GNUC__)
224
# pragma GCC diagnostic push
225
# pragma GCC diagnostic ignored "-Wconversion"
226
# if __GNUC__ >= 6
227
# pragma GCC diagnostic ignored "-Wnull-dereference"
228
# endif
229
#endif
230
231
namespace detail {
232
233
/* python command to get the values below:
234
def dec(v):
235
return str(v)
236
for bits in (8, 16, 32, 64):
237
imin, imax, umax = (-(1 << (bits - 1))), (1 << (bits - 1)) - 1, (1 << bits) - 1
238
for vname, v in (("imin", imin), ("imax", imax), ("umax", umax)):
239
for f in (bin, oct, dec, hex):
240
print(f"{bits}b: {vname}={v} {f.__name__}: len={len(f(v)):2d}: {v} {f(v)}")
241
*/
242
243
// do not use the type as the template argument because in some
244
// platforms long!=int32 and long!=int64. Just use the numbytes
245
// which is more generic and spares lengthy SFINAE code.
246
template<size_t num_bytes, bool is_signed> struct charconv_digits_;
247
template<class T> using charconv_digits = charconv_digits_<sizeof(T), std::is_signed<T>::value>;
248
249
template<> struct charconv_digits_<1u, true> // int8_t
250
{
251
enum : size_t {
252
maxdigits_bin = 1 + 2 + 8, // -128==-0b10000000
253
maxdigits_oct = 1 + 2 + 3, // -128==-0o200
254
maxdigits_dec = 1 + 3, // -128
255
maxdigits_hex = 1 + 2 + 2, // -128==-0x80
256
maxdigits_bin_nopfx = 8, // -128==-0b10000000
257
maxdigits_oct_nopfx = 3, // -128==-0o200
258
maxdigits_dec_nopfx = 3, // -128
259
maxdigits_hex_nopfx = 2, // -128==-0x80
260
};
261
// min values without sign!
262
static constexpr csubstr min_value_dec() noexcept { return csubstr("128"); }
263
static constexpr csubstr min_value_hex() noexcept { return csubstr("80"); }
264
static constexpr csubstr min_value_oct() noexcept { return csubstr("200"); }
265
static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000"); }
266
static constexpr csubstr max_value_dec() noexcept { return csubstr("127"); }
267
static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str[0] <= '1')); }
268
};
269
template<> struct charconv_digits_<1u, false> // uint8_t
270
{
271
enum : size_t {
272
maxdigits_bin = 2 + 8, // 255 0b11111111
273
maxdigits_oct = 2 + 3, // 255 0o377
274
maxdigits_dec = 3, // 255
275
maxdigits_hex = 2 + 2, // 255 0xff
276
maxdigits_bin_nopfx = 8, // 255 0b11111111
277
maxdigits_oct_nopfx = 3, // 255 0o377
278
maxdigits_dec_nopfx = 3, // 255
279
maxdigits_hex_nopfx = 2, // 255 0xff
280
};
281
static constexpr csubstr max_value_dec() noexcept { return csubstr("255"); }
282
static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 3) || (str.len == 3 && str[0] <= '3')); }
283
};
284
template<> struct charconv_digits_<2u, true> // int16_t
285
{
286
enum : size_t {
287
maxdigits_bin = 1 + 2 + 16, // -32768 -0b1000000000000000
288
maxdigits_oct = 1 + 2 + 6, // -32768 -0o100000
289
maxdigits_dec = 1 + 5, // -32768 -32768
290
maxdigits_hex = 1 + 2 + 4, // -32768 -0x8000
291
maxdigits_bin_nopfx = 16, // -32768 -0b1000000000000000
292
maxdigits_oct_nopfx = 6, // -32768 -0o100000
293
maxdigits_dec_nopfx = 5, // -32768 -32768
294
maxdigits_hex_nopfx = 4, // -32768 -0x8000
295
};
296
// min values without sign!
297
static constexpr csubstr min_value_dec() noexcept { return csubstr("32768"); }
298
static constexpr csubstr min_value_hex() noexcept { return csubstr("8000"); }
299
static constexpr csubstr min_value_oct() noexcept { return csubstr("100000"); }
300
static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000"); }
301
static constexpr csubstr max_value_dec() noexcept { return csubstr("32767"); }
302
static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 6)); }
303
};
304
template<> struct charconv_digits_<2u, false> // uint16_t
305
{
306
enum : size_t {
307
maxdigits_bin = 2 + 16, // 65535 0b1111111111111111
308
maxdigits_oct = 2 + 6, // 65535 0o177777
309
maxdigits_dec = 6, // 65535 65535
310
maxdigits_hex = 2 + 4, // 65535 0xffff
311
maxdigits_bin_nopfx = 16, // 65535 0b1111111111111111
312
maxdigits_oct_nopfx = 6, // 65535 0o177777
313
maxdigits_dec_nopfx = 6, // 65535 65535
314
maxdigits_hex_nopfx = 4, // 65535 0xffff
315
};
316
static constexpr csubstr max_value_dec() noexcept { return csubstr("65535"); }
317
static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 6) || (str.len == 6 && str[0] <= '1')); }
318
};
319
template<> struct charconv_digits_<4u, true> // int32_t
320
{
321
enum : size_t {
322
maxdigits_bin = 1 + 2 + 32, // len=35: -2147483648 -0b10000000000000000000000000000000
323
maxdigits_oct = 1 + 2 + 11, // len=14: -2147483648 -0o20000000000
324
maxdigits_dec = 1 + 10, // len=11: -2147483648 -2147483648
325
maxdigits_hex = 1 + 2 + 8, // len=11: -2147483648 -0x80000000
326
maxdigits_bin_nopfx = 32, // len=35: -2147483648 -0b10000000000000000000000000000000
327
maxdigits_oct_nopfx = 11, // len=14: -2147483648 -0o20000000000
328
maxdigits_dec_nopfx = 10, // len=11: -2147483648 -2147483648
329
maxdigits_hex_nopfx = 8, // len=11: -2147483648 -0x80000000
330
};
331
// min values without sign!
332
static constexpr csubstr min_value_dec() noexcept { return csubstr("2147483648"); }
333
static constexpr csubstr min_value_hex() noexcept { return csubstr("80000000"); }
334
static constexpr csubstr min_value_oct() noexcept { return csubstr("20000000000"); }
335
static constexpr csubstr min_value_bin() noexcept { return csubstr("10000000000000000000000000000000"); }
336
static constexpr csubstr max_value_dec() noexcept { return csubstr("2147483647"); }
337
static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str[0] <= '1')); }
338
};
339
template<> struct charconv_digits_<4u, false> // uint32_t
340
{
341
enum : size_t {
342
maxdigits_bin = 2 + 32, // len=34: 4294967295 0b11111111111111111111111111111111
343
maxdigits_oct = 2 + 11, // len=13: 4294967295 0o37777777777
344
maxdigits_dec = 10, // len=10: 4294967295 4294967295
345
maxdigits_hex = 2 + 8, // len=10: 4294967295 0xffffffff
346
maxdigits_bin_nopfx = 32, // len=34: 4294967295 0b11111111111111111111111111111111
347
maxdigits_oct_nopfx = 11, // len=13: 4294967295 0o37777777777
348
maxdigits_dec_nopfx = 10, // len=10: 4294967295 4294967295
349
maxdigits_hex_nopfx = 8, // len=10: 4294967295 0xffffffff
350
};
351
static constexpr csubstr max_value_dec() noexcept { return csubstr("4294967295"); }
352
static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 11) || (str.len == 11 && str[0] <= '3')); }
353
};
354
template<> struct charconv_digits_<8u, true> // int32_t
355
{
356
enum : size_t {
357
maxdigits_bin = 1 + 2 + 64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000
358
maxdigits_oct = 1 + 2 + 22, // len=25: -9223372036854775808 -0o1000000000000000000000
359
maxdigits_dec = 1 + 19, // len=20: -9223372036854775808 -9223372036854775808
360
maxdigits_hex = 1 + 2 + 16, // len=19: -9223372036854775808 -0x8000000000000000
361
maxdigits_bin_nopfx = 64, // len=67: -9223372036854775808 -0b1000000000000000000000000000000000000000000000000000000000000000
362
maxdigits_oct_nopfx = 22, // len=25: -9223372036854775808 -0o1000000000000000000000
363
maxdigits_dec_nopfx = 19, // len=20: -9223372036854775808 -9223372036854775808
364
maxdigits_hex_nopfx = 16, // len=19: -9223372036854775808 -0x8000000000000000
365
};
366
static constexpr csubstr min_value_dec() noexcept { return csubstr("9223372036854775808"); }
367
static constexpr csubstr min_value_hex() noexcept { return csubstr("8000000000000000"); }
368
static constexpr csubstr min_value_oct() noexcept { return csubstr("1000000000000000000000"); }
369
static constexpr csubstr min_value_bin() noexcept { return csubstr("1000000000000000000000000000000000000000000000000000000000000000"); }
370
static constexpr csubstr max_value_dec() noexcept { return csubstr("9223372036854775807"); }
371
static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 22)); }
372
};
373
template<> struct charconv_digits_<8u, false>
374
{
375
enum : size_t {
376
maxdigits_bin = 2 + 64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111
377
maxdigits_oct = 2 + 22, // len=24: 18446744073709551615 0o1777777777777777777777
378
maxdigits_dec = 20, // len=20: 18446744073709551615 18446744073709551615
379
maxdigits_hex = 2 + 16, // len=18: 18446744073709551615 0xffffffffffffffff
380
maxdigits_bin_nopfx = 64, // len=66: 18446744073709551615 0b1111111111111111111111111111111111111111111111111111111111111111
381
maxdigits_oct_nopfx = 22, // len=24: 18446744073709551615 0o1777777777777777777777
382
maxdigits_dec_nopfx = 20, // len=20: 18446744073709551615 18446744073709551615
383
maxdigits_hex_nopfx = 16, // len=18: 18446744073709551615 0xffffffffffffffff
384
};
385
static constexpr csubstr max_value_dec() noexcept { return csubstr("18446744073709551615"); }
386
static constexpr bool is_oct_overflow(csubstr str) noexcept { return !((str.len < 22) || (str.len == 22 && str[0] <= '1')); }
387
};
388
} // namespace detail
389
390
391
//-----------------------------------------------------------------------------
392
//-----------------------------------------------------------------------------
393
//-----------------------------------------------------------------------------
394
395
// Helper macros, undefined below
396
#define _c4append(c) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = static_cast<char>(c); } else { ++pos; } }
397
#define _c4appendhex(i) { if(C4_LIKELY(pos < buf.len)) { buf.str[pos++] = hexchars[i]; } else { ++pos; } }
398
399
/** @name digits_dec return the number of digits required to encode a
400
* decimal number.
401
*
402
* @note At first sight this code may look heavily branchy and
403
* therefore inefficient. However, measurements revealed this to be
404
* the fastest among the alternatives.
405
*
406
* @see https://github.com/biojppm/c4core/pull/77 */
407
/** @{ */
408
409
template<class T>
410
C4_CONSTEXPR14 C4_ALWAYS_INLINE
411
auto digits_dec(T v) noexcept
412
-> typename std::enable_if<sizeof(T) == 1u, unsigned>::type
413
{
414
C4_STATIC_ASSERT(std::is_integral<T>::value);
415
C4_ASSERT(v >= 0);
416
return ((v >= 100) ? 3u : ((v >= 10) ? 2u : 1u));
417
}
418
419
template<class T>
420
C4_CONSTEXPR14 C4_ALWAYS_INLINE
421
auto digits_dec(T v) noexcept
422
-> typename std::enable_if<sizeof(T) == 2u, unsigned>::type
423
{
424
C4_STATIC_ASSERT(std::is_integral<T>::value);
425
C4_ASSERT(v >= 0);
426
return ((v >= 10000) ? 5u : (v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u);
427
}
428
429
template<class T>
430
C4_CONSTEXPR14 C4_ALWAYS_INLINE
431
auto digits_dec(T v) noexcept
432
-> typename std::enable_if<sizeof(T) == 4u, unsigned>::type
433
{
434
C4_STATIC_ASSERT(std::is_integral<T>::value);
435
C4_ASSERT(v >= 0);
436
return ((v >= 1000000000) ? 10u : (v >= 100000000) ? 9u : (v >= 10000000) ? 8u :
437
(v >= 1000000) ? 7u : (v >= 100000) ? 6u : (v >= 10000) ? 5u :
438
(v >= 1000) ? 4u : (v >= 100) ? 3u : (v >= 10) ? 2u : 1u);
439
}
440
441
template<class T>
442
C4_CONSTEXPR14 C4_ALWAYS_INLINE
443
auto digits_dec(T v) noexcept
444
-> typename std::enable_if<sizeof(T) == 8u, unsigned>::type
445
{
446
// thanks @fargies!!!
447
// https://github.com/biojppm/c4core/pull/77#issuecomment-1063753568
448
C4_STATIC_ASSERT(std::is_integral<T>::value);
449
C4_ASSERT(v >= 0);
450
if(v >= 1000000000) // 10
451
{
452
if(v >= 100000000000000) // 15 [15-20] range
453
{
454
if(v >= 100000000000000000) // 18 (15 + (20 - 15) / 2)
455
{
456
if((typename std::make_unsigned<T>::type)v >= 10000000000000000000u) // 20
457
return 20u;
458
else
459
return (v >= 1000000000000000000) ? 19u : 18u;
460
}
461
else if(v >= 10000000000000000) // 17
462
return 17u;
463
else
464
return(v >= 1000000000000000) ? 16u : 15u;
465
}
466
else if(v >= 1000000000000) // 13
467
return (v >= 10000000000000) ? 14u : 13u;
468
else if(v >= 100000000000) // 12
469
return 12;
470
else
471
return(v >= 10000000000) ? 11u : 10u;
472
}
473
else if(v >= 10000) // 5 [5-9] range
474
{
475
if(v >= 10000000) // 8
476
return (v >= 100000000) ? 9u : 8u;
477
else if(v >= 1000000) // 7
478
return 7;
479
else
480
return (v >= 100000) ? 6u : 5u;
481
}
482
else if(v >= 100)
483
return (v >= 1000) ? 4u : 3u;
484
else
485
return (v >= 10) ? 2u : 1u;
486
}
487
488
/** @} */
489
490
491
template<class T>
492
C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_hex(T v) noexcept
493
{
494
C4_STATIC_ASSERT(std::is_integral<T>::value);
495
C4_ASSERT(v >= 0);
496
return v ? 1u + (msb((typename std::make_unsigned<T>::type)v) >> 2u) : 1u;
497
}
498
499
template<class T>
500
C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_bin(T v) noexcept
501
{
502
C4_STATIC_ASSERT(std::is_integral<T>::value);
503
C4_ASSERT(v >= 0);
504
return v ? 1u + msb((typename std::make_unsigned<T>::type)v) : 1u;
505
}
506
507
template<class T>
508
C4_CONSTEXPR14 C4_ALWAYS_INLINE unsigned digits_oct(T v_) noexcept
509
{
510
// TODO: is there a better way?
511
C4_STATIC_ASSERT(std::is_integral<T>::value);
512
C4_ASSERT(v_ >= 0);
513
using U = typename
514
std::conditional<sizeof(T) <= sizeof(unsigned),
515
unsigned,
516
typename std::make_unsigned<T>::type>::type;
517
U v = (U) v_; // safe because we require v_ >= 0
518
unsigned __n = 1;
519
const unsigned __b2 = 64u;
520
const unsigned __b3 = __b2 * 8u;
521
const unsigned long __b4 = __b3 * 8u;
522
while(true)
523
{
524
if(v < 8u)
525
return __n;
526
if(v < __b2)
527
return __n + 1;
528
if(v < __b3)
529
return __n + 2;
530
if(v < __b4)
531
return __n + 3;
532
v /= (U) __b4;
533
__n += 4;
534
}
535
}
536
537
538
//-----------------------------------------------------------------------------
539
//-----------------------------------------------------------------------------
540
//-----------------------------------------------------------------------------
541
542
namespace detail {
543
C4_INLINE_CONSTEXPR const char hexchars[] = "0123456789abcdef";
544
C4_INLINE_CONSTEXPR const char digits0099[] =
545
"0001020304050607080910111213141516171819"
546
"2021222324252627282930313233343536373839"
547
"4041424344454647484950515253545556575859"
548
"6061626364656667686970717273747576777879"
549
"8081828384858687888990919293949596979899";
550
} // namespace detail
551
552
C4_SUPPRESS_WARNING_GCC_PUSH
553
C4_SUPPRESS_WARNING_GCC("-Warray-bounds") // gcc has false positives here
554
#if (defined(__GNUC__) && (__GNUC__ >= 7))
555
C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has false positives here
556
#endif
557
558
template<class T>
559
C4_HOT C4_ALWAYS_INLINE
560
void write_dec_unchecked(substr buf, T v, unsigned digits_v) noexcept
561
{
562
C4_STATIC_ASSERT(std::is_integral<T>::value);
563
C4_ASSERT(v >= 0);
564
C4_ASSERT(buf.len >= digits_v);
565
C4_XASSERT(digits_v == digits_dec(v));
566
// in bm_xtoa: checkoncelog_singlediv_write2
567
while(v >= T(100))
568
{
569
T quo = v;
570
quo /= T(100);
571
const auto num = (v - quo * T(100)) << 1u;
572
v = quo;
573
buf.str[--digits_v] = detail::digits0099[num + 1];
574
buf.str[--digits_v] = detail::digits0099[num];
575
}
576
if(v >= T(10))
577
{
578
C4_ASSERT(digits_v == 2);
579
const auto num = v << 1u;
580
buf.str[1] = detail::digits0099[num + 1];
581
buf.str[0] = detail::digits0099[num];
582
}
583
else
584
{
585
C4_ASSERT(digits_v == 1);
586
buf.str[0] = (char)('0' + v);
587
}
588
}
589
590
591
template<class T>
592
C4_HOT C4_ALWAYS_INLINE
593
void write_hex_unchecked(substr buf, T v, unsigned digits_v) noexcept
594
{
595
C4_STATIC_ASSERT(std::is_integral<T>::value);
596
C4_ASSERT(v >= 0);
597
C4_ASSERT(buf.len >= digits_v);
598
C4_XASSERT(digits_v == digits_hex(v));
599
do {
600
buf.str[--digits_v] = detail::hexchars[v & T(15)];
601
v >>= 4;
602
} while(v);
603
C4_ASSERT(digits_v == 0);
604
}
605
606
607
template<class T>
608
C4_HOT C4_ALWAYS_INLINE
609
void write_oct_unchecked(substr buf, T v, unsigned digits_v) noexcept
610
{
611
C4_STATIC_ASSERT(std::is_integral<T>::value);
612
C4_ASSERT(v >= 0);
613
C4_ASSERT(buf.len >= digits_v);
614
C4_XASSERT(digits_v == digits_oct(v));
615
do {
616
buf.str[--digits_v] = (char)('0' + (v & T(7)));
617
v >>= 3;
618
} while(v);
619
C4_ASSERT(digits_v == 0);
620
}
621
622
623
template<class T>
624
C4_HOT C4_ALWAYS_INLINE
625
void write_bin_unchecked(substr buf, T v, unsigned digits_v) noexcept
626
{
627
C4_STATIC_ASSERT(std::is_integral<T>::value);
628
C4_ASSERT(v >= 0);
629
C4_ASSERT(buf.len >= digits_v);
630
C4_XASSERT(digits_v == digits_bin(v));
631
do {
632
buf.str[--digits_v] = (char)('0' + (v & T(1)));
633
v >>= 1;
634
} while(v);
635
C4_ASSERT(digits_v == 0);
636
}
637
638
639
/** write an integer to a string in decimal format. This is the
640
* lowest level (and the fastest) function to do this task.
641
* @note does not accept negative numbers
642
* @note the resulting string is NOT zero-terminated.
643
* @note it is ok to call this with an empty or too-small buffer;
644
* no writes will occur, and the required size will be returned
645
* @return the number of characters required for the buffer. */
646
template<class T>
647
C4_ALWAYS_INLINE size_t write_dec(substr buf, T v) noexcept
648
{
649
C4_STATIC_ASSERT(std::is_integral<T>::value);
650
C4_ASSERT(v >= 0);
651
unsigned digits = digits_dec(v);
652
if(C4_LIKELY(buf.len >= digits))
653
write_dec_unchecked(buf, v, digits);
654
return digits;
655
}
656
657
/** write an integer to a string in hexadecimal format. This is the
658
* lowest level (and the fastest) function to do this task.
659
* @note does not accept negative numbers
660
* @note does not prefix with 0x
661
* @note the resulting string is NOT zero-terminated.
662
* @note it is ok to call this with an empty or too-small buffer;
663
* no writes will occur, and the required size will be returned
664
* @return the number of characters required for the buffer. */
665
template<class T>
666
C4_ALWAYS_INLINE size_t write_hex(substr buf, T v) noexcept
667
{
668
C4_STATIC_ASSERT(std::is_integral<T>::value);
669
C4_ASSERT(v >= 0);
670
unsigned digits = digits_hex(v);
671
if(C4_LIKELY(buf.len >= digits))
672
write_hex_unchecked(buf, v, digits);
673
return digits;
674
}
675
676
/** write an integer to a string in octal format. This is the
677
* lowest level (and the fastest) function to do this task.
678
* @note does not accept negative numbers
679
* @note does not prefix with 0o
680
* @note the resulting string is NOT zero-terminated.
681
* @note it is ok to call this with an empty or too-small buffer;
682
* no writes will occur, and the required size will be returned
683
* @return the number of characters required for the buffer. */
684
template<class T>
685
C4_ALWAYS_INLINE size_t write_oct(substr buf, T v) noexcept
686
{
687
C4_STATIC_ASSERT(std::is_integral<T>::value);
688
C4_ASSERT(v >= 0);
689
unsigned digits = digits_oct(v);
690
if(C4_LIKELY(buf.len >= digits))
691
write_oct_unchecked(buf, v, digits);
692
return digits;
693
}
694
695
/** write an integer to a string in binary format. This is the
696
* lowest level (and the fastest) function to do this task.
697
* @note does not accept negative numbers
698
* @note does not prefix with 0b
699
* @note the resulting string is NOT zero-terminated.
700
* @note it is ok to call this with an empty or too-small buffer;
701
* no writes will occur, and the required size will be returned
702
* @return the number of characters required for the buffer. */
703
template<class T>
704
C4_ALWAYS_INLINE size_t write_bin(substr buf, T v) noexcept
705
{
706
C4_STATIC_ASSERT(std::is_integral<T>::value);
707
C4_ASSERT(v >= 0);
708
unsigned digits = digits_bin(v);
709
C4_ASSERT(digits > 0);
710
if(C4_LIKELY(buf.len >= digits))
711
write_bin_unchecked(buf, v, digits);
712
return digits;
713
}
714
715
716
namespace detail {
717
template<class U> using NumberWriter = size_t (*)(substr, U);
718
template<class T, NumberWriter<T> writer>
719
size_t write_num_digits(substr buf, T v, size_t num_digits) noexcept
720
{
721
C4_STATIC_ASSERT(std::is_integral<T>::value);
722
size_t ret = writer(buf, v);
723
if(ret >= num_digits)
724
return ret;
725
else if(ret >= buf.len || num_digits > buf.len)
726
return num_digits;
727
C4_ASSERT(num_digits >= ret);
728
size_t delta = static_cast<size_t>(num_digits - ret);
729
memmove(buf.str + delta, buf.str, ret);
730
memset(buf.str, '0', delta);
731
return num_digits;
732
}
733
} // namespace detail
734
735
736
/** same as c4::write_dec(), but pad with zeroes on the left
737
* such that the resulting string is @p num_digits wide.
738
* If the given number is requires more than num_digits, then the number prevails. */
739
template<class T>
740
C4_ALWAYS_INLINE size_t write_dec(substr buf, T val, size_t num_digits) noexcept
741
{
742
return detail::write_num_digits<T, &write_dec<T>>(buf, val, num_digits);
743
}
744
745
/** same as c4::write_hex(), but pad with zeroes on the left
746
* such that the resulting string is @p num_digits wide.
747
* If the given number is requires more than num_digits, then the number prevails. */
748
template<class T>
749
C4_ALWAYS_INLINE size_t write_hex(substr buf, T val, size_t num_digits) noexcept
750
{
751
return detail::write_num_digits<T, &write_hex<T>>(buf, val, num_digits);
752
}
753
754
/** same as c4::write_bin(), but pad with zeroes on the left
755
* such that the resulting string is @p num_digits wide.
756
* If the given number is requires more than num_digits, then the number prevails. */
757
template<class T>
758
C4_ALWAYS_INLINE size_t write_bin(substr buf, T val, size_t num_digits) noexcept
759
{
760
return detail::write_num_digits<T, &write_bin<T>>(buf, val, num_digits);
761
}
762
763
/** same as c4::write_oct(), but pad with zeroes on the left
764
* such that the resulting string is @p num_digits wide.
765
* If the given number is requires more than num_digits, then the number prevails. */
766
template<class T>
767
C4_ALWAYS_INLINE size_t write_oct(substr buf, T val, size_t num_digits) noexcept
768
{
769
return detail::write_num_digits<T, &write_oct<T>>(buf, val, num_digits);
770
}
771
772
C4_SUPPRESS_WARNING_GCC_POP
773
774
775
//-----------------------------------------------------------------------------
776
//-----------------------------------------------------------------------------
777
//-----------------------------------------------------------------------------
778
779
780
C4_SUPPRESS_WARNING_MSVC_PUSH
781
C4_SUPPRESS_WARNING_MSVC(4365) // '=': conversion from 'int' to 'I', signed/unsigned mismatch
782
783
/** read a decimal integer from a string. This is the
784
* lowest level (and the fastest) function to do this task.
785
* @note does not accept negative numbers
786
* @note The string must be trimmed. Whitespace is not accepted.
787
* @note the string must not be empty
788
* @note there is no check for overflow; the value wraps around
789
* in a way similar to the standard C/C++ overflow behavior.
790
* For example, `read_dec<int8_t>("128", &val)` returns true
791
* and val will be set to 0 because 127 is the max i8 value.
792
* @see overflows<T>() to find out if a number string overflows a type range
793
* @return true if the conversion was successful (no overflow check) */
794
template<class I>
795
C4_ALWAYS_INLINE bool read_dec(csubstr s, I *C4_RESTRICT v) noexcept
796
{
797
C4_STATIC_ASSERT(std::is_integral<I>::value);
798
C4_ASSERT(!s.empty());
799
*v = 0;
800
for(char c : s)
801
{
802
if(C4_UNLIKELY(c < '0' || c > '9'))
803
return false;
804
*v = (*v) * I(10) + (I(c) - I('0'));
805
}
806
return true;
807
}
808
809
/** read an hexadecimal integer from a string. This is the
810
* lowest level (and the fastest) function to do this task.
811
* @note does not accept negative numbers
812
* @note does not accept leading 0x or 0X
813
* @note the string must not be empty
814
* @note the string must be trimmed. Whitespace is not accepted.
815
* @note there is no check for overflow; the value wraps around
816
* in a way similar to the standard C/C++ overflow behavior.
817
* For example, `read_hex<int8_t>("80", &val)` returns true
818
* and val will be set to 0 because 7f is the max i8 value.
819
* @see overflows<T>() to find out if a number string overflows a type range
820
* @return true if the conversion was successful (no overflow check) */
821
template<class I>
822
C4_ALWAYS_INLINE bool read_hex(csubstr s, I *C4_RESTRICT v) noexcept
823
{
824
C4_STATIC_ASSERT(std::is_integral<I>::value);
825
C4_ASSERT(!s.empty());
826
*v = 0;
827
for(char c : s)
828
{
829
I cv;
830
if(c >= '0' && c <= '9')
831
cv = I(c) - I('0');
832
else if(c >= 'a' && c <= 'f')
833
cv = I(10) + (I(c) - I('a'));
834
else if(c >= 'A' && c <= 'F')
835
cv = I(10) + (I(c) - I('A'));
836
else
837
return false;
838
*v = (*v) * I(16) + cv;
839
}
840
return true;
841
}
842
843
/** read a binary integer from a string. This is the
844
* lowest level (and the fastest) function to do this task.
845
* @note does not accept negative numbers
846
* @note does not accept leading 0b or 0B
847
* @note the string must not be empty
848
* @note the string must be trimmed. Whitespace is not accepted.
849
* @note there is no check for overflow; the value wraps around
850
* in a way similar to the standard C/C++ overflow behavior.
851
* For example, `read_bin<int8_t>("10000000", &val)` returns true
852
* and val will be set to 0 because 1111111 is the max i8 value.
853
* @see overflows<T>() to find out if a number string overflows a type range
854
* @return true if the conversion was successful (no overflow check) */
855
template<class I>
856
C4_ALWAYS_INLINE bool read_bin(csubstr s, I *C4_RESTRICT v) noexcept
857
{
858
C4_STATIC_ASSERT(std::is_integral<I>::value);
859
C4_ASSERT(!s.empty());
860
*v = 0;
861
for(char c : s)
862
{
863
*v <<= 1;
864
if(c == '1')
865
*v |= 1;
866
else if(c != '0')
867
return false;
868
}
869
return true;
870
}
871
872
/** read an octal integer from a string. This is the
873
* lowest level (and the fastest) function to do this task.
874
* @note does not accept negative numbers
875
* @note does not accept leading 0o or 0O
876
* @note the string must not be empty
877
* @note the string must be trimmed. Whitespace is not accepted.
878
* @note there is no check for overflow; the value wraps around
879
* in a way similar to the standard C/C++ overflow behavior.
880
* For example, `read_oct<int8_t>("200", &val)` returns true
881
* and val will be set to 0 because 177 is the max i8 value.
882
* @see overflows<T>() to find out if a number string overflows a type range
883
* @return true if the conversion was successful (no overflow check) */
884
template<class I>
885
C4_ALWAYS_INLINE bool read_oct(csubstr s, I *C4_RESTRICT v) noexcept
886
{
887
C4_STATIC_ASSERT(std::is_integral<I>::value);
888
C4_ASSERT(!s.empty());
889
*v = 0;
890
for(char c : s)
891
{
892
if(C4_UNLIKELY(c < '0' || c > '7'))
893
return false;
894
*v = (*v) * I(8) + (I(c) - I('0'));
895
}
896
return true;
897
}
898
899
C4_SUPPRESS_WARNING_MSVC_POP
900
901
902
//-----------------------------------------------------------------------------
903
//-----------------------------------------------------------------------------
904
//-----------------------------------------------------------------------------
905
906
C4_SUPPRESS_WARNING_GCC_WITH_PUSH("-Wswitch-default")
907
908
namespace detail {
909
inline size_t _itoa2buf(substr buf, size_t pos, csubstr val) noexcept
910
{
911
C4_ASSERT(pos + val.len <= buf.len);
912
memcpy(buf.str + pos, val.str, val.len);
913
return pos + val.len;
914
}
915
inline size_t _itoa2bufwithdigits(substr buf, size_t pos, size_t num_digits, csubstr val) noexcept
916
{
917
num_digits = num_digits > val.len ? num_digits - val.len : 0;
918
C4_ASSERT(num_digits + val.len <= buf.len);
919
for(size_t i = 0; i < num_digits; ++i)
920
_c4append('0');
921
return detail::_itoa2buf(buf, pos, val);
922
}
923
template<class I>
924
C4_NO_INLINE size_t _itoadec2buf(substr buf) noexcept
925
{
926
using digits_type = detail::charconv_digits<I>;
927
if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec))
928
return digits_type::maxdigits_dec;
929
buf.str[0] = '-';
930
return detail::_itoa2buf(buf, 1, digits_type::min_value_dec());
931
}
932
template<class I>
933
C4_NO_INLINE size_t _itoa2buf(substr buf, I radix) noexcept
934
{
935
using digits_type = detail::charconv_digits<I>;
936
size_t pos = 0;
937
if(C4_LIKELY(buf.len > 0))
938
buf.str[pos++] = '-';
939
switch(radix)
940
{
941
case I(10):
942
if(C4_UNLIKELY(buf.len < digits_type::maxdigits_dec))
943
return digits_type::maxdigits_dec;
944
pos =_itoa2buf(buf, pos, digits_type::min_value_dec());
945
break;
946
case I(16):
947
if(C4_UNLIKELY(buf.len < digits_type::maxdigits_hex))
948
return digits_type::maxdigits_hex;
949
buf.str[pos++] = '0';
950
buf.str[pos++] = 'x';
951
pos = _itoa2buf(buf, pos, digits_type::min_value_hex());
952
break;
953
case I( 2):
954
if(C4_UNLIKELY(buf.len < digits_type::maxdigits_bin))
955
return digits_type::maxdigits_bin;
956
buf.str[pos++] = '0';
957
buf.str[pos++] = 'b';
958
pos = _itoa2buf(buf, pos, digits_type::min_value_bin());
959
break;
960
case I( 8):
961
if(C4_UNLIKELY(buf.len < digits_type::maxdigits_oct))
962
return digits_type::maxdigits_oct;
963
buf.str[pos++] = '0';
964
buf.str[pos++] = 'o';
965
pos = _itoa2buf(buf, pos, digits_type::min_value_oct());
966
break;
967
}
968
return pos;
969
}
970
template<class I>
971
C4_NO_INLINE size_t _itoa2buf(substr buf, I radix, size_t num_digits) noexcept
972
{
973
using digits_type = detail::charconv_digits<I>;
974
size_t pos = 0;
975
size_t needed_digits = 0;
976
if(C4_LIKELY(buf.len > 0))
977
buf.str[pos++] = '-';
978
switch(radix)
979
{
980
case I(10):
981
// add 1 to account for -
982
needed_digits = num_digits+1 > digits_type::maxdigits_dec ? num_digits+1 : digits_type::maxdigits_dec;
983
if(C4_UNLIKELY(buf.len < needed_digits))
984
return needed_digits;
985
pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_dec());
986
break;
987
case I(16):
988
// add 3 to account for -0x
989
needed_digits = num_digits+3 > digits_type::maxdigits_hex ? num_digits+3 : digits_type::maxdigits_hex;
990
if(C4_UNLIKELY(buf.len < needed_digits))
991
return needed_digits;
992
buf.str[pos++] = '0';
993
buf.str[pos++] = 'x';
994
pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_hex());
995
break;
996
case I(2):
997
// add 3 to account for -0b
998
needed_digits = num_digits+3 > digits_type::maxdigits_bin ? num_digits+3 : digits_type::maxdigits_bin;
999
if(C4_UNLIKELY(buf.len < needed_digits))
1000
return needed_digits;
1001
C4_ASSERT(buf.len >= digits_type::maxdigits_bin);
1002
buf.str[pos++] = '0';
1003
buf.str[pos++] = 'b';
1004
pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_bin());
1005
break;
1006
case I(8):
1007
// add 3 to account for -0o
1008
needed_digits = num_digits+3 > digits_type::maxdigits_oct ? num_digits+3 : digits_type::maxdigits_oct;
1009
if(C4_UNLIKELY(buf.len < needed_digits))
1010
return needed_digits;
1011
C4_ASSERT(buf.len >= digits_type::maxdigits_oct);
1012
buf.str[pos++] = '0';
1013
buf.str[pos++] = 'o';
1014
pos = _itoa2bufwithdigits(buf, pos, num_digits, digits_type::min_value_oct());
1015
break;
1016
}
1017
return pos;
1018
}
1019
} // namespace detail
1020
1021
1022
/** convert an integral signed decimal to a string.
1023
* @note the resulting string is NOT zero-terminated.
1024
* @note it is ok to call this with an empty or too-small buffer;
1025
* no writes will occur, and the needed size will be returned
1026
* @return the number of characters required for the buffer. */
1027
template<class T>
1028
C4_ALWAYS_INLINE size_t itoa(substr buf, T v) noexcept
1029
{
1030
C4_STATIC_ASSERT(std::is_signed<T>::value);
1031
if(v >= T(0))
1032
{
1033
// write_dec() checks the buffer size, so no need to check here
1034
return write_dec(buf, v);
1035
}
1036
// when T is the min value (eg i8: -128), negating it
1037
// will overflow, so treat the min as a special case
1038
else if(C4_LIKELY(v != std::numeric_limits<T>::min()))
1039
{
1040
v = -v;
1041
unsigned digits = digits_dec(v);
1042
if(C4_LIKELY(buf.len >= digits + 1u))
1043
{
1044
buf.str[0] = '-';
1045
write_dec_unchecked(buf.sub(1), v, digits);
1046
}
1047
return digits + 1u;
1048
}
1049
return detail::_itoadec2buf<T>(buf);
1050
}
1051
1052
/** convert an integral signed integer to a string, using a specific
1053
* radix. The radix must be 2, 8, 10 or 16.
1054
*
1055
* @note the resulting string is NOT zero-terminated.
1056
* @note it is ok to call this with an empty or too-small buffer;
1057
* no writes will occur, and the needed size will be returned
1058
* @return the number of characters required for the buffer. */
1059
template<class T>
1060
C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix) noexcept
1061
{
1062
C4_STATIC_ASSERT(std::is_signed<T>::value);
1063
C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16);
1064
C4_SUPPRESS_WARNING_GCC_PUSH
1065
#if (defined(__GNUC__) && (__GNUC__ >= 7))
1066
C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has a false positive here
1067
#endif
1068
// when T is the min value (eg i8: -128), negating it
1069
// will overflow, so treat the min as a special case
1070
if(C4_LIKELY(v != std::numeric_limits<T>::min()))
1071
{
1072
unsigned pos = 0;
1073
if(v < 0)
1074
{
1075
v = -v;
1076
if(C4_LIKELY(buf.len > 0))
1077
buf.str[pos] = '-';
1078
++pos;
1079
}
1080
unsigned digits = 0;
1081
switch(radix)
1082
{
1083
case T(10):
1084
digits = digits_dec(v);
1085
if(C4_LIKELY(buf.len >= pos + digits))
1086
write_dec_unchecked(buf.sub(pos), v, digits);
1087
break;
1088
case T(16):
1089
digits = digits_hex(v);
1090
if(C4_LIKELY(buf.len >= pos + 2u + digits))
1091
{
1092
buf.str[pos + 0] = '0';
1093
buf.str[pos + 1] = 'x';
1094
write_hex_unchecked(buf.sub(pos + 2), v, digits);
1095
}
1096
digits += 2u;
1097
break;
1098
case T(2):
1099
digits = digits_bin(v);
1100
if(C4_LIKELY(buf.len >= pos + 2u + digits))
1101
{
1102
buf.str[pos + 0] = '0';
1103
buf.str[pos + 1] = 'b';
1104
write_bin_unchecked(buf.sub(pos + 2), v, digits);
1105
}
1106
digits += 2u;
1107
break;
1108
case T(8):
1109
digits = digits_oct(v);
1110
if(C4_LIKELY(buf.len >= pos + 2u + digits))
1111
{
1112
buf.str[pos + 0] = '0';
1113
buf.str[pos + 1] = 'o';
1114
write_oct_unchecked(buf.sub(pos + 2), v, digits);
1115
}
1116
digits += 2u;
1117
break;
1118
}
1119
return pos + digits;
1120
}
1121
C4_SUPPRESS_WARNING_GCC_POP
1122
// when T is the min value (eg i8: -128), negating it
1123
// will overflow
1124
return detail::_itoa2buf<T>(buf, radix);
1125
}
1126
1127
1128
/** same as c4::itoa(), but pad with zeroes on the left such that the
1129
* resulting string is @p num_digits wide, not accounting for radix
1130
* prefix (0x,0o,0b). The @p radix must be 2, 8, 10 or 16.
1131
*
1132
* @note the resulting string is NOT zero-terminated.
1133
* @note it is ok to call this with an empty or too-small buffer;
1134
* no writes will occur, and the needed size will be returned
1135
* @return the number of characters required for the buffer. */
1136
template<class T>
1137
C4_ALWAYS_INLINE size_t itoa(substr buf, T v, T radix, size_t num_digits) noexcept
1138
{
1139
C4_STATIC_ASSERT(std::is_signed<T>::value);
1140
C4_ASSERT(radix == 2 || radix == 8 || radix == 10 || radix == 16);
1141
C4_SUPPRESS_WARNING_GCC_PUSH
1142
#if (defined(__GNUC__) && (__GNUC__ >= 7))
1143
C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow") // gcc has a false positive here
1144
#endif
1145
// when T is the min value (eg i8: -128), negating it
1146
// will overflow, so treat the min as a special case
1147
if(C4_LIKELY(v != std::numeric_limits<T>::min()))
1148
{
1149
unsigned pos = 0;
1150
if(v < 0)
1151
{
1152
v = -v;
1153
if(C4_LIKELY(buf.len > 0))
1154
buf.str[pos] = '-';
1155
++pos;
1156
}
1157
unsigned total_digits = 0;
1158
switch(radix)
1159
{
1160
case T(10):
1161
total_digits = digits_dec(v);
1162
total_digits = pos + (unsigned)(num_digits > total_digits ? num_digits : total_digits);
1163
if(C4_LIKELY(buf.len >= total_digits))
1164
write_dec(buf.sub(pos), v, num_digits);
1165
break;
1166
case T(16):
1167
total_digits = digits_hex(v);
1168
total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);
1169
if(C4_LIKELY(buf.len >= total_digits))
1170
{
1171
buf.str[pos + 0] = '0';
1172
buf.str[pos + 1] = 'x';
1173
write_hex(buf.sub(pos + 2), v, num_digits);
1174
}
1175
break;
1176
case T(2):
1177
total_digits = digits_bin(v);
1178
total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);
1179
if(C4_LIKELY(buf.len >= total_digits))
1180
{
1181
buf.str[pos + 0] = '0';
1182
buf.str[pos + 1] = 'b';
1183
write_bin(buf.sub(pos + 2), v, num_digits);
1184
}
1185
break;
1186
case T(8):
1187
total_digits = digits_oct(v);
1188
total_digits = pos + 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);
1189
if(C4_LIKELY(buf.len >= total_digits))
1190
{
1191
buf.str[pos + 0] = '0';
1192
buf.str[pos + 1] = 'o';
1193
write_oct(buf.sub(pos + 2), v, num_digits);
1194
}
1195
break;
1196
}
1197
return total_digits;
1198
}
1199
C4_SUPPRESS_WARNING_GCC_POP
1200
// when T is the min value (eg i8: -128), negating it
1201
// will overflow
1202
return detail::_itoa2buf<T>(buf, radix, num_digits);
1203
}
1204
1205
1206
//-----------------------------------------------------------------------------
1207
//-----------------------------------------------------------------------------
1208
//-----------------------------------------------------------------------------
1209
1210
/** convert an integral unsigned decimal to a string.
1211
*
1212
* @note the resulting string is NOT zero-terminated.
1213
* @note it is ok to call this with an empty or too-small buffer;
1214
* no writes will occur, and the needed size will be returned
1215
* @return the number of characters required for the buffer. */
1216
template<class T>
1217
C4_ALWAYS_INLINE size_t utoa(substr buf, T v) noexcept
1218
{
1219
C4_STATIC_ASSERT(std::is_unsigned<T>::value);
1220
// write_dec() does the buffer length check, so no need to check here
1221
return write_dec(buf, v);
1222
}
1223
1224
/** convert an integral unsigned integer to a string, using a specific
1225
* radix. The radix must be 2, 8, 10 or 16.
1226
*
1227
* @note the resulting string is NOT zero-terminated.
1228
* @note it is ok to call this with an empty or too-small buffer;
1229
* no writes will occur, and the needed size will be returned
1230
* @return the number of characters required for the buffer. */
1231
template<class T>
1232
C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix) noexcept
1233
{
1234
C4_STATIC_ASSERT(std::is_unsigned<T>::value);
1235
C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8);
1236
unsigned digits = 0;
1237
switch(radix)
1238
{
1239
case T(10):
1240
digits = digits_dec(v);
1241
if(C4_LIKELY(buf.len >= digits))
1242
write_dec_unchecked(buf, v, digits);
1243
break;
1244
case T(16):
1245
digits = digits_hex(v);
1246
if(C4_LIKELY(buf.len >= digits+2u))
1247
{
1248
buf.str[0] = '0';
1249
buf.str[1] = 'x';
1250
write_hex_unchecked(buf.sub(2), v, digits);
1251
}
1252
digits += 2u;
1253
break;
1254
case T(2):
1255
digits = digits_bin(v);
1256
if(C4_LIKELY(buf.len >= digits+2u))
1257
{
1258
buf.str[0] = '0';
1259
buf.str[1] = 'b';
1260
write_bin_unchecked(buf.sub(2), v, digits);
1261
}
1262
digits += 2u;
1263
break;
1264
case T(8):
1265
digits = digits_oct(v);
1266
if(C4_LIKELY(buf.len >= digits+2u))
1267
{
1268
buf.str[0] = '0';
1269
buf.str[1] = 'o';
1270
write_oct_unchecked(buf.sub(2), v, digits);
1271
}
1272
digits += 2u;
1273
break;
1274
}
1275
return digits;
1276
}
1277
1278
/** same as c4::utoa(), but pad with zeroes on the left such that the
1279
* resulting string is @p num_digits wide. The @p radix must be 2,
1280
* 8, 10 or 16.
1281
*
1282
* @note the resulting string is NOT zero-terminated.
1283
* @note it is ok to call this with an empty or too-small buffer;
1284
* no writes will occur, and the needed size will be returned
1285
* @return the number of characters required for the buffer. */
1286
template<class T>
1287
C4_ALWAYS_INLINE size_t utoa(substr buf, T v, T radix, size_t num_digits) noexcept
1288
{
1289
C4_STATIC_ASSERT(std::is_unsigned<T>::value);
1290
C4_ASSERT(radix == 10 || radix == 16 || radix == 2 || radix == 8);
1291
unsigned total_digits = 0;
1292
switch(radix)
1293
{
1294
case T(10):
1295
total_digits = digits_dec(v);
1296
total_digits = (unsigned)(num_digits > total_digits ? num_digits : total_digits);
1297
if(C4_LIKELY(buf.len >= total_digits))
1298
write_dec(buf, v, num_digits);
1299
break;
1300
case T(16):
1301
total_digits = digits_hex(v);
1302
total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);
1303
if(C4_LIKELY(buf.len >= total_digits))
1304
{
1305
buf.str[0] = '0';
1306
buf.str[1] = 'x';
1307
write_hex(buf.sub(2), v, num_digits);
1308
}
1309
break;
1310
case T(2):
1311
total_digits = digits_bin(v);
1312
total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);
1313
if(C4_LIKELY(buf.len >= total_digits))
1314
{
1315
buf.str[0] = '0';
1316
buf.str[1] = 'b';
1317
write_bin(buf.sub(2), v, num_digits);
1318
}
1319
break;
1320
case T(8):
1321
total_digits = digits_oct(v);
1322
total_digits = 2u + (unsigned)(num_digits > total_digits ? num_digits : total_digits);
1323
if(C4_LIKELY(buf.len >= total_digits))
1324
{
1325
buf.str[0] = '0';
1326
buf.str[1] = 'o';
1327
write_oct(buf.sub(2), v, num_digits);
1328
}
1329
break;
1330
}
1331
return total_digits;
1332
}
1333
C4_SUPPRESS_WARNING_GCC_POP
1334
1335
1336
//-----------------------------------------------------------------------------
1337
//-----------------------------------------------------------------------------
1338
//-----------------------------------------------------------------------------
1339
1340
/** Convert a trimmed string to a signed integral value. The input
1341
* string can be formatted as decimal, binary (prefix 0b or 0B), octal
1342
* (prefix 0o or 0O) or hexadecimal (prefix 0x or 0X). Strings with
1343
* leading zeroes are considered as decimal and not octal (unlike the
1344
* C/C++ convention). Every character in the input string is read for
1345
* the conversion; the input string must not contain any leading or
1346
* trailing whitespace.
1347
*
1348
* @return true if the conversion was successful.
1349
*
1350
* @note overflow is not detected: the return status is true even if
1351
* the conversion would return a value outside of the type's range, in
1352
* which case the result will wrap around the type's range.
1353
* This is similar to native behavior.
1354
*
1355
* @note a positive sign is not accepted. ie, the string must not
1356
* start with '+'
1357
*
1358
* @see atoi_first() if the string is not trimmed to the value to read. */
1359
template<class T>
1360
C4_ALWAYS_INLINE bool atoi(csubstr str, T * C4_RESTRICT v) noexcept
1361
{
1362
C4_STATIC_ASSERT(std::is_integral<T>::value);
1363
C4_STATIC_ASSERT(std::is_signed<T>::value);
1364
1365
if(C4_UNLIKELY(str.len == 0))
1366
return false;
1367
1368
C4_ASSERT(str.str[0] != '+');
1369
1370
T sign = 1;
1371
size_t start = 0;
1372
if(str.str[0] == '-')
1373
{
1374
if(C4_UNLIKELY(str.len == ++start))
1375
return false;
1376
sign = -1;
1377
}
1378
1379
bool parsed_ok = true;
1380
if(str.str[start] != '0') // this should be the common case, so put it first
1381
{
1382
parsed_ok = read_dec(str.sub(start), v);
1383
}
1384
else if(str.len > start + 1)
1385
{
1386
// starts with 0: is it 0x, 0o, 0b?
1387
const char pfx = str.str[start + 1];
1388
if(pfx == 'x' || pfx == 'X')
1389
parsed_ok = str.len > start + 2 && read_hex(str.sub(start + 2), v);
1390
else if(pfx == 'b' || pfx == 'B')
1391
parsed_ok = str.len > start + 2 && read_bin(str.sub(start + 2), v);
1392
else if(pfx == 'o' || pfx == 'O')
1393
parsed_ok = str.len > start + 2 && read_oct(str.sub(start + 2), v);
1394
else
1395
parsed_ok = read_dec(str.sub(start + 1), v);
1396
}
1397
else
1398
{
1399
parsed_ok = read_dec(str.sub(start), v);
1400
}
1401
if(C4_LIKELY(parsed_ok))
1402
*v *= sign;
1403
return parsed_ok;
1404
}
1405
1406
1407
/** Select the next range of characters in the string that can be parsed
1408
* as a signed integral value, and convert it using atoi(). Leading
1409
* whitespace (space, newline, tabs) is skipped.
1410
* @return the number of characters read for conversion, or csubstr::npos if the conversion failed
1411
* @see atoi() if the string is already trimmed to the value to read.
1412
* @see csubstr::first_int_span() */
1413
template<class T>
1414
C4_ALWAYS_INLINE size_t atoi_first(csubstr str, T * C4_RESTRICT v)
1415
{
1416
csubstr trimmed = str.first_int_span();
1417
if(trimmed.len == 0)
1418
return csubstr::npos;
1419
if(atoi(trimmed, v))
1420
return static_cast<size_t>(trimmed.end() - str.begin());
1421
return csubstr::npos;
1422
}
1423
1424
1425
//-----------------------------------------------------------------------------
1426
1427
/** Convert a trimmed string to an unsigned integral value. The string can be
1428
* formatted as decimal, binary (prefix 0b or 0B), octal (prefix 0o or 0O)
1429
* or hexadecimal (prefix 0x or 0X). Every character in the input string is read
1430
* for the conversion; it must not contain any leading or trailing whitespace.
1431
*
1432
* @return true if the conversion was successful.
1433
*
1434
* @note overflow is not detected: the return status is true even if
1435
* the conversion would return a value outside of the type's range, in
1436
* which case the result will wrap around the type's range.
1437
*
1438
* @note If the string has a minus character, the return status
1439
* will be false.
1440
*
1441
* @see atou_first() if the string is not trimmed to the value to read. */
1442
template<class T>
1443
bool atou(csubstr str, T * C4_RESTRICT v) noexcept
1444
{
1445
C4_STATIC_ASSERT(std::is_integral<T>::value);
1446
1447
if(C4_UNLIKELY(str.len == 0 || str.front() == '-'))
1448
return false;
1449
1450
bool parsed_ok = true;
1451
if(str.str[0] != '0')
1452
{
1453
parsed_ok = read_dec(str, v);
1454
}
1455
else
1456
{
1457
if(str.len > 1)
1458
{
1459
const char pfx = str.str[1];
1460
if(pfx == 'x' || pfx == 'X')
1461
parsed_ok = str.len > 2 && read_hex(str.sub(2), v);
1462
else if(pfx == 'b' || pfx == 'B')
1463
parsed_ok = str.len > 2 && read_bin(str.sub(2), v);
1464
else if(pfx == 'o' || pfx == 'O')
1465
parsed_ok = str.len > 2 && read_oct(str.sub(2), v);
1466
else
1467
parsed_ok = read_dec(str, v);
1468
}
1469
else
1470
{
1471
*v = 0; // we know the first character is 0
1472
}
1473
}
1474
return parsed_ok;
1475
}
1476
1477
1478
/** Select the next range of characters in the string that can be parsed
1479
* as an unsigned integral value, and convert it using atou(). Leading
1480
* whitespace (space, newline, tabs) is skipped.
1481
* @return the number of characters read for conversion, or csubstr::npos if the conversion faileds
1482
* @see atou() if the string is already trimmed to the value to read.
1483
* @see csubstr::first_uint_span() */
1484
template<class T>
1485
C4_ALWAYS_INLINE size_t atou_first(csubstr str, T *v)
1486
{
1487
csubstr trimmed = str.first_uint_span();
1488
if(trimmed.len == 0)
1489
return csubstr::npos;
1490
if(atou(trimmed, v))
1491
return static_cast<size_t>(trimmed.end() - str.begin());
1492
return csubstr::npos;
1493
}
1494
1495
1496
#ifdef _MSC_VER
1497
# pragma warning(pop)
1498
#elif defined(__clang__)
1499
# pragma clang diagnostic pop
1500
#elif defined(__GNUC__)
1501
# pragma GCC diagnostic pop
1502
#endif
1503
1504
1505
//-----------------------------------------------------------------------------
1506
//-----------------------------------------------------------------------------
1507
//-----------------------------------------------------------------------------
1508
namespace detail {
1509
inline bool check_overflow(csubstr str, csubstr limit) noexcept
1510
{
1511
if(str.len == limit.len)
1512
{
1513
for(size_t i = 0; i < limit.len; ++i)
1514
{
1515
if(str[i] < limit[i])
1516
return false;
1517
else if(str[i] > limit[i])
1518
return true;
1519
}
1520
return false;
1521
}
1522
else
1523
return str.len > limit.len;
1524
}
1525
} // namespace detail
1526
1527
1528
/** Test if the following string would overflow when converted to associated
1529
* types.
1530
* @return true if number will overflow, false if it fits (or doesn't parse)
1531
*/
1532
template<class T>
1533
auto overflows(csubstr str) noexcept
1534
-> typename std::enable_if<std::is_unsigned<T>::value, bool>::type
1535
{
1536
C4_STATIC_ASSERT(std::is_integral<T>::value);
1537
1538
if(C4_UNLIKELY(str.len == 0))
1539
{
1540
return false;
1541
}
1542
else if(str.str[0] == '0')
1543
{
1544
if (str.len == 1)
1545
return false;
1546
switch (str.str[1])
1547
{
1548
case 'x':
1549
case 'X':
1550
{
1551
size_t fno = str.first_not_of('0', 2);
1552
if (fno == csubstr::npos)
1553
return false;
1554
return !(str.len <= fno + (sizeof(T) * 2));
1555
}
1556
case 'b':
1557
case 'B':
1558
{
1559
size_t fno = str.first_not_of('0', 2);
1560
if (fno == csubstr::npos)
1561
return false;
1562
return !(str.len <= fno +(sizeof(T) * 8));
1563
}
1564
case 'o':
1565
case 'O':
1566
{
1567
size_t fno = str.first_not_of('0', 2);
1568
if(fno == csubstr::npos)
1569
return false;
1570
return detail::charconv_digits<T>::is_oct_overflow(str.sub(fno));
1571
}
1572
default:
1573
{
1574
size_t fno = str.first_not_of('0', 1);
1575
if(fno == csubstr::npos)
1576
return false;
1577
return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::max_value_dec());
1578
}
1579
}
1580
}
1581
else if(C4_UNLIKELY(str[0] == '-'))
1582
{
1583
return true;
1584
}
1585
else
1586
{
1587
return detail::check_overflow(str, detail::charconv_digits<T>::max_value_dec());
1588
}
1589
}
1590
1591
1592
/** Test if the following string would overflow when converted to associated
1593
* types.
1594
* @return true if number will overflow, false if it fits (or doesn't parse)
1595
*/
1596
template<class T>
1597
auto overflows(csubstr str)
1598
-> typename std::enable_if<std::is_signed<T>::value, bool>::type
1599
{
1600
C4_STATIC_ASSERT(std::is_integral<T>::value);
1601
if(C4_UNLIKELY(str.len == 0))
1602
return false;
1603
if(str.str[0] == '-')
1604
{
1605
if(str.str[1] == '0')
1606
{
1607
if(str.len == 2)
1608
return false;
1609
switch(str.str[2])
1610
{
1611
case 'x':
1612
case 'X':
1613
{
1614
size_t fno = str.first_not_of('0', 3);
1615
if (fno == csubstr::npos)
1616
return false;
1617
return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_hex());
1618
}
1619
case 'b':
1620
case 'B':
1621
{
1622
size_t fno = str.first_not_of('0', 3);
1623
if (fno == csubstr::npos)
1624
return false;
1625
return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_bin());
1626
}
1627
case 'o':
1628
case 'O':
1629
{
1630
size_t fno = str.first_not_of('0', 3);
1631
if(fno == csubstr::npos)
1632
return false;
1633
return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_oct());
1634
}
1635
default:
1636
{
1637
size_t fno = str.first_not_of('0', 2);
1638
if(fno == csubstr::npos)
1639
return false;
1640
return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::min_value_dec());
1641
}
1642
}
1643
}
1644
else
1645
return detail::check_overflow(str.sub(1), detail::charconv_digits<T>::min_value_dec());
1646
}
1647
else if(str.str[0] == '0')
1648
{
1649
if (str.len == 1)
1650
return false;
1651
switch(str.str[1])
1652
{
1653
case 'x':
1654
case 'X':
1655
{
1656
size_t fno = str.first_not_of('0', 2);
1657
if (fno == csubstr::npos)
1658
return false;
1659
const size_t len = str.len - fno;
1660
return !((len < sizeof (T) * 2) || (len == sizeof(T) * 2 && str[fno] <= '7'));
1661
}
1662
case 'b':
1663
case 'B':
1664
{
1665
size_t fno = str.first_not_of('0', 2);
1666
if (fno == csubstr::npos)
1667
return false;
1668
return !(str.len <= fno + (sizeof(T) * 8 - 1));
1669
}
1670
case 'o':
1671
case 'O':
1672
{
1673
size_t fno = str.first_not_of('0', 2);
1674
if(fno == csubstr::npos)
1675
return false;
1676
return detail::charconv_digits<T>::is_oct_overflow(str.sub(fno));
1677
}
1678
default:
1679
{
1680
size_t fno = str.first_not_of('0', 1);
1681
if(fno == csubstr::npos)
1682
return false;
1683
return detail::check_overflow(str.sub(fno), detail::charconv_digits<T>::max_value_dec());
1684
}
1685
}
1686
}
1687
else
1688
return detail::check_overflow(str, detail::charconv_digits<T>::max_value_dec());
1689
}
1690
1691
1692
//-----------------------------------------------------------------------------
1693
//-----------------------------------------------------------------------------
1694
//-----------------------------------------------------------------------------
1695
1696
namespace detail {
1697
1698
1699
#if (!C4CORE_HAVE_STD_FROMCHARS)
1700
/** @see http://www.exploringbinary.com/ for many good examples on float-str conversion */
1701
template<size_t N>
1702
void get_real_format_str(char (& C4_RESTRICT fmt)[N], int precision, RealFormat_e formatting, const char* length_modifier="")
1703
{
1704
int iret;
1705
if(precision == -1)
1706
iret = snprintf(fmt, sizeof(fmt), "%%%s%c", length_modifier, formatting);
1707
else if(precision == 0)
1708
iret = snprintf(fmt, sizeof(fmt), "%%.%s%c", length_modifier, formatting);
1709
else
1710
iret = snprintf(fmt, sizeof(fmt), "%%.%d%s%c", precision, length_modifier, formatting);
1711
C4_ASSERT(iret >= 2 && size_t(iret) < sizeof(fmt));
1712
C4_UNUSED(iret);
1713
}
1714
1715
1716
/** @todo we're depending on snprintf()/sscanf() for converting to/from
1717
* floating point numbers. Apparently, this increases the binary size
1718
* by a considerable amount. There are some lightweight printf
1719
* implementations:
1720
*
1721
* @see http://www.sparetimelabs.com/tinyprintf/tinyprintf.php (BSD)
1722
* @see https://github.com/weiss/c99-snprintf
1723
* @see https://github.com/nothings/stb/blob/master/stb_sprintf.h
1724
* @see http://www.exploringbinary.com/
1725
* @see https://blog.benoitblanchon.fr/lightweight-float-to-string/
1726
* @see http://www.ryanjuckett.com/programming/printing-floating-point-numbers/
1727
*/
1728
template<class T>
1729
size_t print_one(substr str, const char* full_fmt, T v)
1730
{
1731
#ifdef _MSC_VER
1732
/** use _snprintf() to prevent early termination of the output
1733
* for writing the null character at the last position
1734
* @see https://msdn.microsoft.com/en-us/library/2ts7cx93.aspx */
1735
int iret = _snprintf(str.str, str.len, full_fmt, v);
1736
if(iret < 0)
1737
{
1738
/* when buf.len is not enough, VS returns a negative value.
1739
* so call it again with a negative value for getting an
1740
* actual length of the string */
1741
iret = snprintf(nullptr, 0, full_fmt, v);
1742
C4_ASSERT(iret > 0);
1743
}
1744
size_t ret = (size_t) iret;
1745
return ret;
1746
#else
1747
int iret = snprintf(str.str, str.len, full_fmt, v);
1748
C4_ASSERT(iret >= 0);
1749
size_t ret = (size_t) iret;
1750
if(ret >= str.len)
1751
++ret; /* snprintf() reserves the last character to write \0 */
1752
return ret;
1753
#endif
1754
}
1755
#endif // (!C4CORE_HAVE_STD_FROMCHARS)
1756
1757
1758
#if (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT)
1759
/** scans a string using the given type format, while at the same time
1760
* allowing non-null-terminated strings AND guaranteeing that the given
1761
* string length is strictly respected, so that no buffer overflows
1762
* might occur. */
1763
template<typename T>
1764
inline size_t scan_one(csubstr str, const char *type_fmt, T *v)
1765
{
1766
/* snscanf() is absolutely needed here as we must be sure that
1767
* str.len is strictly respected, because substr is
1768
* generally not null-terminated.
1769
*
1770
* Alas, there is no snscanf().
1771
*
1772
* So we fake it by using a dynamic format with an explicit
1773
* field size set to the length of the given span.
1774
* This trick is taken from:
1775
* https://stackoverflow.com/a/18368910/5875572 */
1776
1777
/* this is the actual format we'll use for scanning */
1778
char fmt[16];
1779
1780
/* write the length into it. Eg "%12f".
1781
* Also, get the number of characters read from the string.
1782
* So the final format ends up as "%12f%n"*/
1783
int iret = std::snprintf(fmt, sizeof(fmt), "%%" "%zu" "%s" "%%n", str.len, type_fmt);
1784
/* no nasty surprises, please! */
1785
C4_ASSERT(iret >= 0 && size_t(iret) < C4_COUNTOF(fmt));
1786
1787
/* now we scan with confidence that the span length is respected */
1788
int num_chars;
1789
iret = std::sscanf(str.str, fmt, v, &num_chars);
1790
/* scanf returns the number of successful conversions */
1791
if(iret != 1) return csubstr::npos;
1792
C4_ASSERT(num_chars >= 0);
1793
return (size_t)(num_chars);
1794
}
1795
#endif // (!C4CORE_HAVE_STD_FROMCHARS) && (!C4CORE_HAVE_FAST_FLOAT)
1796
1797
1798
#if C4CORE_HAVE_STD_TOCHARS
1799
template<class T>
1800
C4_ALWAYS_INLINE size_t rtoa(substr buf, T v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept
1801
{
1802
std::to_chars_result result;
1803
size_t pos = 0;
1804
if(formatting == FTOA_HEXA)
1805
{
1806
if(buf.len > size_t(2))
1807
{
1808
buf.str[0] = '0';
1809
buf.str[1] = 'x';
1810
}
1811
pos += size_t(2);
1812
}
1813
if(precision == -1)
1814
result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting);
1815
else
1816
result = std::to_chars(buf.str + pos, buf.str + buf.len, v, (std::chars_format)formatting, precision);
1817
if(result.ec == std::errc())
1818
{
1819
// all good, no errors.
1820
C4_ASSERT(result.ptr >= buf.str);
1821
ptrdiff_t delta = result.ptr - buf.str;
1822
return static_cast<size_t>(delta);
1823
}
1824
C4_ASSERT(result.ec == std::errc::value_too_large);
1825
// This is unfortunate.
1826
//
1827
// When the result can't fit in the given buffer,
1828
// std::to_chars() returns the end pointer it was originally
1829
// given, which is useless because here we would like to know
1830
// _exactly_ how many characters the buffer must have to fit
1831
// the result.
1832
//
1833
// So we take the pessimistic view, and assume as many digits
1834
// as could ever be required:
1835
size_t ret = static_cast<size_t>(std::numeric_limits<T>::max_digits10);
1836
return ret > buf.len ? ret : buf.len + 1;
1837
}
1838
#endif // C4CORE_HAVE_STD_TOCHARS
1839
1840
1841
#if C4CORE_HAVE_FAST_FLOAT
1842
template<class T>
1843
C4_ALWAYS_INLINE bool scan_rhex(csubstr s, T *C4_RESTRICT val) noexcept
1844
{
1845
C4_ASSERT(s.len > 0);
1846
C4_ASSERT(s.str[0] != '-');
1847
C4_ASSERT(s.str[0] != '+');
1848
C4_ASSERT(!s.begins_with("0x"));
1849
C4_ASSERT(!s.begins_with("0X"));
1850
size_t pos = 0;
1851
// integer part
1852
for( ; pos < s.len; ++pos)
1853
{
1854
const char c = s.str[pos];
1855
if(c >= '0' && c <= '9')
1856
*val = *val * T(16) + T(c - '0');
1857
else if(c >= 'a' && c <= 'f')
1858
*val = *val * T(16) + T(c - 'a');
1859
else if(c >= 'A' && c <= 'F')
1860
*val = *val * T(16) + T(c - 'A');
1861
else if(c == '.')
1862
{
1863
++pos;
1864
break; // follow on to mantissa
1865
}
1866
else if(c == 'p' || c == 'P')
1867
{
1868
++pos;
1869
goto power; // no mantissa given, jump to power
1870
}
1871
else
1872
{
1873
return false;
1874
}
1875
}
1876
// mantissa
1877
{
1878
// 0.0625 == 1/16 == value of first digit after the comma
1879
for(T digit = T(0.0625); pos < s.len; ++pos, digit /= T(16))
1880
{
1881
const char c = s.str[pos];
1882
if(c >= '0' && c <= '9')
1883
*val += digit * T(c - '0');
1884
else if(c >= 'a' && c <= 'f')
1885
*val += digit * T(c - 'a');
1886
else if(c >= 'A' && c <= 'F')
1887
*val += digit * T(c - 'A');
1888
else if(c == 'p' || c == 'P')
1889
{
1890
++pos;
1891
goto power; // mantissa finished, jump to power
1892
}
1893
else
1894
{
1895
return false;
1896
}
1897
}
1898
}
1899
return true;
1900
power:
1901
if(C4_LIKELY(pos < s.len))
1902
{
1903
if(s.str[pos] == '+') // atoi() cannot handle a leading '+'
1904
++pos;
1905
if(C4_LIKELY(pos < s.len))
1906
{
1907
int16_t powval = {};
1908
if(C4_LIKELY(atoi(s.sub(pos), &powval)))
1909
{
1910
*val *= ipow<T, int16_t, 16>(powval);
1911
return true;
1912
}
1913
}
1914
}
1915
return false;
1916
}
1917
#endif
1918
1919
} // namespace detail
1920
1921
1922
#undef _c4appendhex
1923
#undef _c4append
1924
1925
1926
/** Convert a single-precision real number to string. The string will
1927
* in general be NOT null-terminated. For FTOA_FLEX, \p precision is
1928
* the number of significand digits. Otherwise \p precision is the
1929
* number of decimals. It is safe to call this function with an empty
1930
* or too-small buffer.
1931
*
1932
* @return the size of the buffer needed to write the number
1933
*/
1934
C4_ALWAYS_INLINE size_t ftoa(substr str, float v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept
1935
{
1936
#if C4CORE_HAVE_STD_TOCHARS
1937
return detail::rtoa(str, v, precision, formatting);
1938
#else
1939
char fmt[16];
1940
detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/"");
1941
return detail::print_one(str, fmt, v);
1942
#endif
1943
}
1944
1945
1946
/** Convert a double-precision real number to string. The string will
1947
* in general be NOT null-terminated. For FTOA_FLEX, \p precision is
1948
* the number of significand digits. Otherwise \p precision is the
1949
* number of decimals. It is safe to call this function with an empty
1950
* or too-small buffer.
1951
*
1952
* @return the size of the buffer needed to write the number
1953
*/
1954
C4_ALWAYS_INLINE size_t dtoa(substr str, double v, int precision=-1, RealFormat_e formatting=FTOA_FLEX) noexcept
1955
{
1956
#if C4CORE_HAVE_STD_TOCHARS
1957
return detail::rtoa(str, v, precision, formatting);
1958
#else
1959
char fmt[16];
1960
detail::get_real_format_str(fmt, precision, formatting, /*length_modifier*/"l");
1961
return detail::print_one(str, fmt, v);
1962
#endif
1963
}
1964
1965
1966
/** Convert a string to a single precision real number.
1967
* The input string must be trimmed to the value, ie
1968
* no leading or trailing whitespace can be present.
1969
* @return true iff the conversion succeeded
1970
* @see atof_first() if the string is not trimmed
1971
*/
1972
C4_ALWAYS_INLINE bool atof(csubstr str, float * C4_RESTRICT v) noexcept
1973
{
1974
C4_ASSERT(str.len > 0);
1975
C4_ASSERT(str.triml(" \r\t\n").len == str.len);
1976
#if C4CORE_HAVE_FAST_FLOAT
1977
// fastfloat cannot parse hexadecimal floats
1978
bool isneg = (str.str[0] == '-');
1979
csubstr rem = str.sub(isneg || str.str[0] == '+');
1980
if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X'))))
1981
{
1982
fast_float::from_chars_result result;
1983
result = fast_float::from_chars(str.str, str.str + str.len, *v);
1984
return result.ec == std::errc();
1985
}
1986
else if(detail::scan_rhex(rem.sub(2), v))
1987
{
1988
*v *= isneg ? -1.f : 1.f;
1989
return true;
1990
}
1991
return false;
1992
#elif C4CORE_HAVE_STD_FROMCHARS
1993
std::from_chars_result result;
1994
result = std::from_chars(str.str, str.str + str.len, *v);
1995
return result.ec == std::errc();
1996
#else
1997
csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+');
1998
if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X'))))
1999
return detail::scan_one(str, "f", v) != csubstr::npos;
2000
else
2001
return detail::scan_one(str, "a", v) != csubstr::npos;
2002
#endif
2003
}
2004
2005
2006
/** Convert a string to a double precision real number.
2007
* The input string must be trimmed to the value, ie
2008
* no leading or trailing whitespace can be present.
2009
* @return true iff the conversion succeeded
2010
* @see atod_first() if the string is not trimmed
2011
*/
2012
C4_ALWAYS_INLINE bool atod(csubstr str, double * C4_RESTRICT v) noexcept
2013
{
2014
C4_ASSERT(str.triml(" \r\t\n").len == str.len);
2015
#if C4CORE_HAVE_FAST_FLOAT
2016
// fastfloat cannot parse hexadecimal floats
2017
bool isneg = (str.str[0] == '-');
2018
csubstr rem = str.sub(isneg || str.str[0] == '+');
2019
if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X'))))
2020
{
2021
fast_float::from_chars_result result;
2022
result = fast_float::from_chars(str.str, str.str + str.len, *v);
2023
return result.ec == std::errc();
2024
}
2025
else if(detail::scan_rhex(rem.sub(2), v))
2026
{
2027
*v *= isneg ? -1. : 1.;
2028
return true;
2029
}
2030
return false;
2031
#elif C4CORE_HAVE_STD_FROMCHARS
2032
std::from_chars_result result;
2033
result = std::from_chars(str.str, str.str + str.len, *v);
2034
return result.ec == std::errc();
2035
#else
2036
csubstr rem = str.sub(str.str[0] == '-' || str.str[0] == '+');
2037
if(!(rem.len >= 2 && (rem.str[0] == '0' && (rem.str[1] == 'x' || rem.str[1] == 'X'))))
2038
return detail::scan_one(str, "lf", v) != csubstr::npos;
2039
else
2040
return detail::scan_one(str, "la", v) != csubstr::npos;
2041
#endif
2042
}
2043
2044
2045
/** Convert a string to a single precision real number.
2046
* Leading whitespace is skipped until valid characters are found.
2047
* @return the number of characters read from the string, or npos if
2048
* conversion was not successful or if the string was empty */
2049
inline size_t atof_first(csubstr str, float * C4_RESTRICT v) noexcept
2050
{
2051
csubstr trimmed = str.first_real_span();
2052
if(trimmed.len == 0)
2053
return csubstr::npos;
2054
if(atof(trimmed, v))
2055
return static_cast<size_t>(trimmed.end() - str.begin());
2056
return csubstr::npos;
2057
}
2058
2059
2060
/** Convert a string to a double precision real number.
2061
* Leading whitespace is skipped until valid characters are found.
2062
* @return the number of characters read from the string, or npos if
2063
* conversion was not successful or if the string was empty */
2064
inline size_t atod_first(csubstr str, double * C4_RESTRICT v) noexcept
2065
{
2066
csubstr trimmed = str.first_real_span();
2067
if(trimmed.len == 0)
2068
return csubstr::npos;
2069
if(atod(trimmed, v))
2070
return static_cast<size_t>(trimmed.end() - str.begin());
2071
return csubstr::npos;
2072
}
2073
2074
2075
//-----------------------------------------------------------------------------
2076
//-----------------------------------------------------------------------------
2077
//-----------------------------------------------------------------------------
2078
// generic versions
2079
2080
C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v) noexcept { return write_dec(s, v); }
2081
C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v) noexcept { return write_dec(s, v); }
2082
C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v) noexcept { return write_dec(s, v); }
2083
C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v) noexcept { return write_dec(s, v); }
2084
C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v) noexcept { return itoa(s, v); }
2085
C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v) noexcept { return itoa(s, v); }
2086
C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v) noexcept { return itoa(s, v); }
2087
C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v) noexcept { return itoa(s, v); }
2088
C4_ALWAYS_INLINE size_t xtoa(substr s, float v) noexcept { return ftoa(s, v); }
2089
C4_ALWAYS_INLINE size_t xtoa(substr s, double v) noexcept { return dtoa(s, v); }
2090
2091
C4_ALWAYS_INLINE size_t xtoa(substr s, uint8_t v, uint8_t radix) noexcept { return utoa(s, v, radix); }
2092
C4_ALWAYS_INLINE size_t xtoa(substr s, uint16_t v, uint16_t radix) noexcept { return utoa(s, v, radix); }
2093
C4_ALWAYS_INLINE size_t xtoa(substr s, uint32_t v, uint32_t radix) noexcept { return utoa(s, v, radix); }
2094
C4_ALWAYS_INLINE size_t xtoa(substr s, uint64_t v, uint64_t radix) noexcept { return utoa(s, v, radix); }
2095
C4_ALWAYS_INLINE size_t xtoa(substr s, int8_t v, int8_t radix) noexcept { return itoa(s, v, radix); }
2096
C4_ALWAYS_INLINE size_t xtoa(substr s, int16_t v, int16_t radix) noexcept { return itoa(s, v, radix); }
2097
C4_ALWAYS_INLINE size_t xtoa(substr s, int32_t v, int32_t radix) noexcept { return itoa(s, v, radix); }
2098
C4_ALWAYS_INLINE size_t xtoa(substr s, int64_t v, int64_t radix) noexcept { return itoa(s, v, radix); }
2099
2100
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); }
2101
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); }
2102
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); }
2103
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); }
2104
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); }
2105
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); }
2106
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); }
2107
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); }
2108
2109
C4_ALWAYS_INLINE size_t xtoa(substr s, float v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return ftoa(s, v, precision, formatting); }
2110
C4_ALWAYS_INLINE size_t xtoa(substr s, double v, int precision, RealFormat_e formatting=FTOA_FLEX) noexcept { return dtoa(s, v, precision, formatting); }
2111
2112
C4_ALWAYS_INLINE bool atox(csubstr s, uint8_t *C4_RESTRICT v) noexcept { return atou(s, v); }
2113
C4_ALWAYS_INLINE bool atox(csubstr s, uint16_t *C4_RESTRICT v) noexcept { return atou(s, v); }
2114
C4_ALWAYS_INLINE bool atox(csubstr s, uint32_t *C4_RESTRICT v) noexcept { return atou(s, v); }
2115
C4_ALWAYS_INLINE bool atox(csubstr s, uint64_t *C4_RESTRICT v) noexcept { return atou(s, v); }
2116
C4_ALWAYS_INLINE bool atox(csubstr s, int8_t *C4_RESTRICT v) noexcept { return atoi(s, v); }
2117
C4_ALWAYS_INLINE bool atox(csubstr s, int16_t *C4_RESTRICT v) noexcept { return atoi(s, v); }
2118
C4_ALWAYS_INLINE bool atox(csubstr s, int32_t *C4_RESTRICT v) noexcept { return atoi(s, v); }
2119
C4_ALWAYS_INLINE bool atox(csubstr s, int64_t *C4_RESTRICT v) noexcept { return atoi(s, v); }
2120
C4_ALWAYS_INLINE bool atox(csubstr s, float *C4_RESTRICT v) noexcept { return atof(s, v); }
2121
C4_ALWAYS_INLINE bool atox(csubstr s, double *C4_RESTRICT v) noexcept { return atod(s, v); }
2122
2123
C4_ALWAYS_INLINE size_t to_chars(substr buf, uint8_t v) noexcept { return write_dec(buf, v); }
2124
C4_ALWAYS_INLINE size_t to_chars(substr buf, uint16_t v) noexcept { return write_dec(buf, v); }
2125
C4_ALWAYS_INLINE size_t to_chars(substr buf, uint32_t v) noexcept { return write_dec(buf, v); }
2126
C4_ALWAYS_INLINE size_t to_chars(substr buf, uint64_t v) noexcept { return write_dec(buf, v); }
2127
C4_ALWAYS_INLINE size_t to_chars(substr buf, int8_t v) noexcept { return itoa(buf, v); }
2128
C4_ALWAYS_INLINE size_t to_chars(substr buf, int16_t v) noexcept { return itoa(buf, v); }
2129
C4_ALWAYS_INLINE size_t to_chars(substr buf, int32_t v) noexcept { return itoa(buf, v); }
2130
C4_ALWAYS_INLINE size_t to_chars(substr buf, int64_t v) noexcept { return itoa(buf, v); }
2131
C4_ALWAYS_INLINE size_t to_chars(substr buf, float v) noexcept { return ftoa(buf, v); }
2132
C4_ALWAYS_INLINE size_t to_chars(substr buf, double v) noexcept { return dtoa(buf, v); }
2133
2134
C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint8_t *C4_RESTRICT v) noexcept { return atou(buf, v); }
2135
C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou(buf, v); }
2136
C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou(buf, v); }
2137
C4_ALWAYS_INLINE bool from_chars(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou(buf, v); }
2138
C4_ALWAYS_INLINE bool from_chars(csubstr buf, int8_t *C4_RESTRICT v) noexcept { return atoi(buf, v); }
2139
C4_ALWAYS_INLINE bool from_chars(csubstr buf, int16_t *C4_RESTRICT v) noexcept { return atoi(buf, v); }
2140
C4_ALWAYS_INLINE bool from_chars(csubstr buf, int32_t *C4_RESTRICT v) noexcept { return atoi(buf, v); }
2141
C4_ALWAYS_INLINE bool from_chars(csubstr buf, int64_t *C4_RESTRICT v) noexcept { return atoi(buf, v); }
2142
C4_ALWAYS_INLINE bool from_chars(csubstr buf, float *C4_RESTRICT v) noexcept { return atof(buf, v); }
2143
C4_ALWAYS_INLINE bool from_chars(csubstr buf, double *C4_RESTRICT v) noexcept { return atod(buf, v); }
2144
2145
C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint8_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); }
2146
C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint16_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); }
2147
C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint32_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); }
2148
C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, uint64_t *C4_RESTRICT v) noexcept { return atou_first(buf, v); }
2149
C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int8_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); }
2150
C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int16_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); }
2151
C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int32_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); }
2152
C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, int64_t *C4_RESTRICT v) noexcept { return atoi_first(buf, v); }
2153
C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, float *C4_RESTRICT v) noexcept { return atof_first(buf, v); }
2154
C4_ALWAYS_INLINE size_t from_chars_first(csubstr buf, double *C4_RESTRICT v) noexcept { return atod_first(buf, v); }
2155
2156
2157
//-----------------------------------------------------------------------------
2158
// on some platforms, (unsigned) int and (unsigned) long
2159
// are not any of the fixed length types above
2160
2161
#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>
2162
#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>
2163
2164
template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type xtoa(substr buf, T v) noexcept { return itoa(buf, v); }
2165
template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type xtoa(substr buf, T v) noexcept { return write_dec(buf, v); }
2166
2167
template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, bool )::type atox(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi(buf, v); }
2168
template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, bool )::type atox(csubstr buf, T *C4_RESTRICT v) noexcept { return atou(buf, v); }
2169
2170
template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, size_t)::type to_chars(substr buf, T v) noexcept { return itoa(buf, v); }
2171
template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, size_t)::type to_chars(substr buf, T v) noexcept { return write_dec(buf, v); }
2172
2173
template <class T> _C4_IF_NOT_FIXED_LENGTH_I(T, bool )::type from_chars(csubstr buf, T *C4_RESTRICT v) noexcept { return atoi(buf, v); }
2174
template <class T> _C4_IF_NOT_FIXED_LENGTH_U(T, bool )::type from_chars(csubstr buf, T *C4_RESTRICT v) noexcept { return atou(buf, v); }
2175
2176
template <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); }
2177
template <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); }
2178
2179
#undef _C4_IF_NOT_FIXED_LENGTH_I
2180
#undef _C4_IF_NOT_FIXED_LENGTH_U
2181
2182
2183
//-----------------------------------------------------------------------------
2184
// for pointers
2185
2186
template <class T> C4_ALWAYS_INLINE size_t xtoa(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); }
2187
template <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; }
2188
template <class T> C4_ALWAYS_INLINE size_t to_chars(substr s, T *v) noexcept { return itoa(s, (intptr_t)v, (intptr_t)16); }
2189
template <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; }
2190
template <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; }
2191
2192
2193
//-----------------------------------------------------------------------------
2194
//-----------------------------------------------------------------------------
2195
//-----------------------------------------------------------------------------
2196
/** call to_chars() and return a substr consisting of the
2197
* written portion of the input buffer. Ie, same as to_chars(),
2198
* but return a substr instead of a size_t.
2199
*
2200
* @see to_chars() */
2201
template<class T>
2202
C4_ALWAYS_INLINE substr to_chars_sub(substr buf, T const& C4_RESTRICT v) noexcept
2203
{
2204
size_t sz = to_chars(buf, v);
2205
return buf.left_of(sz <= buf.len ? sz : buf.len);
2206
}
2207
2208
//-----------------------------------------------------------------------------
2209
//-----------------------------------------------------------------------------
2210
//-----------------------------------------------------------------------------
2211
// bool implementation
2212
2213
C4_ALWAYS_INLINE size_t to_chars(substr buf, bool v) noexcept
2214
{
2215
int val = v;
2216
return to_chars(buf, val);
2217
}
2218
2219
inline bool from_chars(csubstr buf, bool * C4_RESTRICT v) noexcept
2220
{
2221
if(buf == '0')
2222
{
2223
*v = false; return true;
2224
}
2225
else if(buf == '1')
2226
{
2227
*v = true; return true;
2228
}
2229
else if(buf == "false")
2230
{
2231
*v = false; return true;
2232
}
2233
else if(buf == "true")
2234
{
2235
*v = true; return true;
2236
}
2237
else if(buf == "False")
2238
{
2239
*v = false; return true;
2240
}
2241
else if(buf == "True")
2242
{
2243
*v = true; return true;
2244
}
2245
else if(buf == "FALSE")
2246
{
2247
*v = false; return true;
2248
}
2249
else if(buf == "TRUE")
2250
{
2251
*v = true; return true;
2252
}
2253
// fallback to c-style int bools
2254
int val = 0;
2255
bool ret = from_chars(buf, &val);
2256
if(C4_LIKELY(ret))
2257
{
2258
*v = (val != 0);
2259
}
2260
return ret;
2261
}
2262
2263
inline size_t from_chars_first(csubstr buf, bool * C4_RESTRICT v) noexcept
2264
{
2265
csubstr trimmed = buf.first_non_empty_span();
2266
if(trimmed.len == 0 || !from_chars(buf, v))
2267
return csubstr::npos;
2268
return trimmed.len;
2269
}
2270
2271
2272
//-----------------------------------------------------------------------------
2273
// single-char implementation
2274
2275
inline size_t to_chars(substr buf, char v) noexcept
2276
{
2277
if(buf.len > 0)
2278
{
2279
C4_XASSERT(buf.str);
2280
buf.str[0] = v;
2281
}
2282
return 1;
2283
}
2284
2285
/** extract a single character from a substring
2286
* @note to extract a string instead and not just a single character, use the csubstr overload */
2287
inline bool from_chars(csubstr buf, char * C4_RESTRICT v) noexcept
2288
{
2289
if(buf.len != 1)
2290
return false;
2291
C4_XASSERT(buf.str);
2292
*v = buf.str[0];
2293
return true;
2294
}
2295
2296
inline size_t from_chars_first(csubstr buf, char * C4_RESTRICT v) noexcept
2297
{
2298
if(buf.len < 1)
2299
return csubstr::npos;
2300
*v = buf.str[0];
2301
return 1;
2302
}
2303
2304
2305
//-----------------------------------------------------------------------------
2306
// csubstr implementation
2307
2308
inline size_t to_chars(substr buf, csubstr v) noexcept
2309
{
2310
C4_ASSERT(!buf.overlaps(v));
2311
size_t len = buf.len < v.len ? buf.len : v.len;
2312
// calling memcpy with null strings is undefined behavior
2313
// and will wreak havoc in calling code's branches.
2314
// see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637
2315
if(len)
2316
{
2317
C4_ASSERT(buf.str != nullptr);
2318
C4_ASSERT(v.str != nullptr);
2319
memcpy(buf.str, v.str, len);
2320
}
2321
return v.len;
2322
}
2323
2324
inline bool from_chars(csubstr buf, csubstr *C4_RESTRICT v) noexcept
2325
{
2326
*v = buf;
2327
return true;
2328
}
2329
2330
inline size_t from_chars_first(substr buf, csubstr * C4_RESTRICT v) noexcept
2331
{
2332
csubstr trimmed = buf.first_non_empty_span();
2333
if(trimmed.len == 0)
2334
return csubstr::npos;
2335
*v = trimmed;
2336
return static_cast<size_t>(trimmed.end() - buf.begin());
2337
}
2338
2339
2340
//-----------------------------------------------------------------------------
2341
// substr
2342
2343
inline size_t to_chars(substr buf, substr v) noexcept
2344
{
2345
C4_ASSERT(!buf.overlaps(v));
2346
size_t len = buf.len < v.len ? buf.len : v.len;
2347
// calling memcpy with null strings is undefined behavior
2348
// and will wreak havoc in calling code's branches.
2349
// see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637
2350
if(len)
2351
{
2352
C4_ASSERT(buf.str != nullptr);
2353
C4_ASSERT(v.str != nullptr);
2354
memcpy(buf.str, v.str, len);
2355
}
2356
return v.len;
2357
}
2358
2359
inline bool from_chars(csubstr buf, substr * C4_RESTRICT v) noexcept
2360
{
2361
C4_ASSERT(!buf.overlaps(*v));
2362
// is the destination buffer wide enough?
2363
if(v->len >= buf.len)
2364
{
2365
// calling memcpy with null strings is undefined behavior
2366
// and will wreak havoc in calling code's branches.
2367
// see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637
2368
if(buf.len)
2369
{
2370
C4_ASSERT(buf.str != nullptr);
2371
C4_ASSERT(v->str != nullptr);
2372
memcpy(v->str, buf.str, buf.len);
2373
}
2374
v->len = buf.len;
2375
return true;
2376
}
2377
return false;
2378
}
2379
2380
inline size_t from_chars_first(csubstr buf, substr * C4_RESTRICT v) noexcept
2381
{
2382
csubstr trimmed = buf.first_non_empty_span();
2383
C4_ASSERT(!trimmed.overlaps(*v));
2384
if(C4_UNLIKELY(trimmed.len == 0))
2385
return csubstr::npos;
2386
size_t len = trimmed.len > v->len ? v->len : trimmed.len;
2387
// calling memcpy with null strings is undefined behavior
2388
// and will wreak havoc in calling code's branches.
2389
// see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637
2390
if(len)
2391
{
2392
C4_ASSERT(buf.str != nullptr);
2393
C4_ASSERT(v->str != nullptr);
2394
memcpy(v->str, trimmed.str, len);
2395
}
2396
if(C4_UNLIKELY(trimmed.len > v->len))
2397
return csubstr::npos;
2398
return static_cast<size_t>(trimmed.end() - buf.begin());
2399
}
2400
2401
2402
//-----------------------------------------------------------------------------
2403
2404
template<size_t N>
2405
inline size_t to_chars(substr buf, const char (& C4_RESTRICT v)[N]) noexcept
2406
{
2407
csubstr sp(v);
2408
return to_chars(buf, sp);
2409
}
2410
2411
inline size_t to_chars(substr buf, const char * C4_RESTRICT v) noexcept
2412
{
2413
return to_chars(buf, to_csubstr(v));
2414
}
2415
2416
} // namespace c4
2417
2418
#ifdef _MSC_VER
2419
# pragma warning(pop)
2420
#endif
2421
2422
#if defined(__clang__)
2423
# pragma clang diagnostic pop
2424
#elif defined(__GNUC__)
2425
# pragma GCC diagnostic pop
2426
#endif
2427
2428
#endif /* _C4_CHARCONV_HPP_ */
2429
2430