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