General Utility Library for C++17 25.4.1
to_number.h
Go to the documentation of this file.
1
23#ifndef GUL17_TO_NUMBER_H_
24#define GUL17_TO_NUMBER_H_
25
26#include <array>
27#include <cmath>
28#include <cstdint>
29#include <cstdlib>
30#include <exception>
31#include <limits>
32#include <optional>
33#include <string_view>
34#include <type_traits>
35
36#include "gul17/internal.h"
38
39namespace gul17 {
40
48namespace detail {
49
50constexpr inline bool is_digit(char c) noexcept
51{
52 return c >= '0' && c <= '9';
53}
54
55constexpr inline bool is_nan_specifier(char c) noexcept
56{
57 if (c >= '0' && c <= '9')
58 return true;
59 if (c >= 'a' && c <= 'z')
60 return true;
61 if (c >= 'A' && c <= 'Z')
62 return true;
63 if (c == '_')
64 return true;
65 return false;
66}
67
68template <typename NumberType, bool count_magnitude = false>
69constexpr inline std::optional<NumberType> to_unsigned_integer(std::string_view str,
70 NumberType* magnitude = nullptr) noexcept
71{
72#ifndef __GNUC__
73 constexpr NumberType max_tenth = std::numeric_limits<NumberType>::max() / 10;
74#endif
75
76 if (str.empty())
77 return std::nullopt;
78
80
81 for (char c : str)
82 {
83 if (!is_digit(c))
84 return std::nullopt;
85
86#ifdef __GNUC__
87 if (__builtin_mul_overflow(result, NumberType{ 10 }, &result)) // NOLINT(cppcoreguidelines-pro-type-vararg)
88 return std::nullopt;
89
90 if (__builtin_add_overflow(result, static_cast<NumberType>(c - '0'), &result)) // NOLINT(cppcoreguidelines-pro-type-vararg)
91 return std::nullopt;
92#else
93 if (result > max_tenth)
94 return std::nullopt;
95
96 result *= 10;
97
98 auto last = result;
99
100 result += c - '0';
101 if (result < last)
102 return std::nullopt;
103#endif
104 if /*constexpr*/ (count_magnitude)
105 *magnitude *= NumberType{ 10 };
106 }
107
108 return result;
109}
110
111/* Parse a signed exponent specifier.
112 * May start with a leading sign ('+' or '-'). The exponent value is limited to
113 * the range of int. The used range with a long double conversion is usually in
114 * the range -5000 to 5000, so this is not really a limitation.
115 */
116constexpr std::optional<int> parse_exponent(std::string_view str) noexcept
117{
118 bool negative = false;
119
120 switch (str.front())
121 {
122 case '+':
123 str.remove_prefix(1);
124 break;
125 case '-':
126 str.remove_prefix(1);
127 negative = true;
128 break;
129 default:
130 break;
131 }
132
134
135 if (!opt_exp)
136 return std::nullopt;
137
138 if (negative)
139 return -*opt_exp;
140 return *opt_exp;
141}
142
143// For some 'long double' types with a big mantissa uint64 is not large enough.
144// We resort to __uint128, which is a non standard extension in GCC and clang.
145// But only if we need to.
146// Note that on some compilers there are no std::numeric_limits<> for the extension
147// type, and then asserts later on will fail. But usually that compilers have small
148// long double types.
149template <typename NumberType>
151 typename std::conditional<
152 (std::numeric_limits<std::uint64_t>::digits10 >= std::numeric_limits<NumberType>::digits10),
153 std::uint64_t,
154 #ifdef __SIZEOF_INT128__ // GCC, clang, intel
156 #else
157 std::uint64_t
158 #endif
159 >::type;
160
177template <typename NumberType>
178constexpr inline std::optional<NumberType> to_normalized_float(std::string_view i1, std::string_view i2) noexcept
179{
180 static_assert(std::numeric_limits<FloatConversionIntType<NumberType>>::digits10
181 >= std::numeric_limits<NumberType>::digits10,
182 "FloatConversionIntType is too small for NumberType");
183
184 i1 = i1.substr(0, std::min(i1.length(),
185 size_t(std::numeric_limits<FloatConversionIntType<NumberType>>::digits10)));
186 i2 = i2.substr(0, std::min(i2.length(),
187 size_t(std::numeric_limits<FloatConversionIntType<NumberType>>::digits10) - i1.length()));
188
190
192
193 if (not i2.empty()) {
195 if (not f2.has_value())
196 return std::nullopt;
197 accu = *f2;
198 }
199 if (not i1.empty()) {
200 auto i2_magnitude = magnitude;
202 if (not f1.has_value())
203 return std::nullopt;
204 accu += (*f1 * i2_magnitude);
205 }
206
207 return static_cast<NumberType>(accu) / static_cast<NumberType>(magnitude / 10); // NOLINT(bugprone-integer-division): Precision loss is not possible with normalized accu
208}
209
210template <typename NumberType>
211struct ParseInfNanResult {
212 bool result_valid;
213 std::optional<NumberType> result;
214};
215
235template <typename NumberType>
236constexpr inline ParseInfNanResult<NumberType> parse_inf_nan(std::string_view str) noexcept
237{
238 auto const strlength = str.length();
239 if (strlength == 0)
240 return { true, {} };
241
242 if (gul17::starts_with_nocase(str, "inf")) {
243 if (strlength == 3 /* strlen("inf") */ )
244 return { true, std::make_optional(std::numeric_limits<NumberType>::infinity()) };
245 if (strlength == 8 /* strlen("infinity") */
246 and gul17::starts_with_nocase(str.substr(3), "inity"))
247 return { true, std::make_optional(std::numeric_limits<NumberType>::infinity()) };
248 return { true, {} };
249 }
250
251 if (gul17::starts_with_nocase(str, "nan")) {
252 if (strlength == 3 /* strlen("nan") */ )
253 return { true, std::make_optional(std::numeric_limits<NumberType>::quiet_NaN()) };
254 if (strlength < 5 /* strlen("nan()") */ or str[3] != '(' or str.back() != ')')
255 return { true, {} };
256 str.remove_prefix(4);
257 str.remove_suffix(1);
258 while (str.length()) {
259 if (not is_nan_specifier(str.front()))
260 return { true, {} };
261 str.remove_prefix(1);
262 }
263 // We do not use the NaN specifier
264 return { true, std::make_optional(std::numeric_limits<NumberType>::quiet_NaN()) };
265 }
266 return { false, {} };
267}
268
280GUL_EXPORT
281long double pow10(int exponent);
282
300template <typename NumberType>
301constexpr inline std::optional<NumberType> to_unsigned_float(std::string_view str) noexcept
302{
304 if (inf_nan.result_valid)
305 return inf_nan.result;
306
307 int exponent = 0;
308 auto e_pos = str.find_first_of("eE");
309 if (e_pos != std::string_view::npos)
310 {
311 if (e_pos + 1 == str.size())
312 return std::nullopt;
313
314 auto str_exponent = str.substr(e_pos + 1);
315
316 str = str.substr(0, e_pos);
317
318 auto opt_exp = detail::parse_exponent(str_exponent);
319
320 if (!opt_exp)
321 return std::nullopt;
322
323 exponent = *opt_exp;
324 }
325
326 std::string_view str_before_point{ str };
327 std::string_view str_after_point;
328
329 auto point_pos = str.find('.');
330 if (point_pos != std::string_view::npos)
331 {
332 str_before_point = str.substr(0, point_pos);
333 str_after_point = str.substr(point_pos + 1);
334 }
335
336 if (str_before_point.empty() && str_after_point.empty())
337 return std::nullopt;
338
339 // Get rid of leading zeros
340 while (!str_before_point.empty() and str_before_point[0] == '0')
341 str_before_point.remove_prefix(1);
342
343 // Normalize the number
344 if (str_before_point.empty()) {
345 auto const old_digits = str_after_point.length();
346 while (!str_after_point.empty() and str_after_point[0] == '0')
347 str_after_point.remove_prefix(1);
348
349 if (str_after_point.empty())
350 return { 0 };
351
352 str_before_point = str_after_point.substr(0, 1);
353 str_after_point.remove_prefix(1);
354 exponent -= static_cast<int>(old_digits - str_after_point.length());
355 } else {
356 exponent += static_cast<int>(str_before_point.length() - 1);
357 }
358
359 // Now the incoming number string is like this:
360 // "s.tr_before_point" "str_after_point" E exponent
361 // ^ ^
362 // | here is the decimal dot, virtually | corrected exponent
363
364 using long_double = long double;
365 using CalcType = std::conditional_t<
366 std::greater<>()(sizeof(NumberType), sizeof(double)),
367 long_double, double>;
368
370 if (not norm_val.has_value())
371 return std::nullopt;
372
373 return static_cast<NumberType>(detail::pow10(exponent) * *norm_val);
374}
375
393template <typename NumberType>
394inline std::optional<NumberType> strtold_wrapper(std::string_view str) noexcept
395{
396 if (str.empty())
397 return std::nullopt;
398
399 try
400 {
401 auto input = std::string{ str };
402 char* process_end;
403 auto value = static_cast<NumberType>(std::strtold(input.c_str(), &process_end));
404
405 if (input.data() + input.size() != process_end) // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic): Pointer arithmetic needed because strtold gives pointer back
406 return std::nullopt;
407 return value;
408 }
409 catch (const std::exception &)
410 {
411 return std::nullopt;
412 }
413}
414
415} // namespace detail
417
418
495// Overload for unsigned integer types.
496template <typename NumberType>
497constexpr inline std::enable_if_t<std::is_integral<NumberType>::value and
498 std::is_unsigned<NumberType>::value,
499 std::optional<NumberType>>
500to_number(std::string_view str) noexcept
501{
502 return detail::to_unsigned_integer<NumberType>(str);
503}
504
505// Overload for signed integer types.
506template <typename NumberType>
507constexpr inline std::enable_if_t<std::is_integral<NumberType>::value and
508 std::is_signed<NumberType>::value,
509 std::optional<NumberType>>
510to_number(std::string_view str) noexcept
511{
512 if (str.empty())
513 return std::nullopt;
514
515 if (str.front() == '-')
516 {
517 using UnsignedT = std::make_unsigned_t<NumberType>;
518 constexpr auto max_abs_negative_value =
519 static_cast<UnsignedT>(std::numeric_limits<NumberType>::max()) + 1;
520
521 str.remove_prefix(1);
522
523 auto result = detail::to_unsigned_integer<UnsignedT>(str);
524 if (!result)
525 return std::nullopt;
526
528 return std::numeric_limits<NumberType>::lowest();
529 else if (*result > max_abs_negative_value)
530 return std::nullopt;
531
532 return static_cast<NumberType>(-static_cast<NumberType>(*result));
533 }
534
535 return detail::to_unsigned_integer<NumberType>(str);
536}
537
538// Overload for floating-point types.
539template <typename NumberType>
540constexpr inline std::enable_if_t<std::is_floating_point<NumberType>::value,
541 std::optional<NumberType>>
542to_number(std::string_view str) noexcept
543{
544 if (str.empty())
545 return std::nullopt;
546
547 if (
550# pragma warning( disable: 4127 ) // conditional expression is constant
551#endif
554#ifdef _MSC_VER
555# pragma warning( pop )
556#endif
557 // Too big for our approach. Resort to non-constexpr functionality.
558 // This actually never happenes with the currently supported platforms / compilers.
559 // (Except long double on Darwin)
560 return detail::strtold_wrapper<NumberType>(str);
561 }
562
563 if (str.front() == '-')
564 {
565 str.remove_prefix(1);
566 auto result = detail::to_unsigned_float<NumberType>(str);
567 if (!result)
568 return std::nullopt;
569 return -*result;
570 }
571
572 return detail::to_unsigned_float<NumberType>(str);
573}
574
575// Overload for bool
576template<>
577constexpr inline std::optional<bool> to_number<bool>(std::string_view str) noexcept
578{
579 if (str.length() == 1) {
580 if (str[0] == '1')
581 return true;
582 if (str[0] == '0')
583 return false;
584 return std::nullopt;
585 }
586 if (equals_nocase(str, "true"))
587 return true;
588
589 if (equals_nocase(str, "false"))
590 return false;
591
592 return std::nullopt;
593}
594
596
597} // namespace gul17
598
599#endif
600
601// vi:ts=4:sw=4:et:sts=4
auto constexpr bit_set(unsigned bit) noexcept -> ReturnT
Set a bit in an integral type.
Definition bit_manip.h:121
constexpr bool equals_nocase(std::string_view str1, std::string_view str2) noexcept
Determine whether a string is equal to another one, making no distinction between upper and lower cas...
Definition substring_checks.h:166
constexpr bool starts_with_nocase(std::string_view str, std::string_view prefix) noexcept
Determine whether a string starts with another string.
Definition substring_checks.h:314
constexpr std::enable_if_t< std::is_integral< NumberType >::value and std::is_unsigned< NumberType >::value, std::optional< NumberType > > to_number(std::string_view str) noexcept
Convert an ASCII std::string_view into a number.
Definition to_number.h:500
Definition of macros used internally by GUL.
Namespace gul17 contains all functions and classes of the General Utility Library.
Definition doxygen.h:26
Definition of contains(), ends_with(), and starts_with().