General Utility Library for C++14  2.8
to_number.h
Go to the documentation of this file.
1 
23 #ifndef GUL14_TO_NUMBER_H_
24 #define GUL14_TO_NUMBER_H_
25 
26 #include <array>
27 #include <cmath>
28 #include <cstdint>
29 #include <cstdlib>
30 #include <exception>
31 #include <limits>
32 #include <type_traits>
33 
34 #include "gul14/internal.h"
35 #include "gul14/optional.h"
36 #include "gul14/string_view.h"
37 #include "gul14/substring_checks.h"
38 
39 namespace gul14 {
40 
42 namespace detail {
43 
44 constexpr inline bool is_digit(char c) noexcept
45 {
46  return c >= '0' && c <= '9';
47 }
48 
49 constexpr inline bool is_nan_specifier(char c) noexcept
50 {
51  if (c >= '0' && c <= '9')
52  return true;
53  if (c >= 'a' && c <= 'z')
54  return true;
55  if (c >= 'A' && c <= 'Z')
56  return true;
57  if (c == '_')
58  return true;
59  return false;
60 }
61 
62 template <typename NumberType, bool count_magnitude = false>
63 constexpr inline optional<NumberType> to_unsigned_integer(gul14::string_view str,
64  NumberType* magnitude = nullptr) noexcept
65 {
66 #ifndef __GNUC__
67  constexpr NumberType max_tenth = std::numeric_limits<NumberType>::max() / 10;
68 #endif
69 
70  if (str.empty())
71  return nullopt;
72 
73  NumberType result = 0;
74 
75  for (char c : str)
76  {
77  if (!is_digit(c))
78  return nullopt;
79 
80 #ifdef __GNUC__
81  if (__builtin_mul_overflow(result, NumberType{ 10 }, &result)) // NOLINT(cppcoreguidelines-pro-type-vararg)
82  return nullopt;
83 
84  if (__builtin_add_overflow(result, static_cast<NumberType>(c - '0'), &result)) // NOLINT(cppcoreguidelines-pro-type-vararg)
85  return nullopt;
86 #else
87  if (result > max_tenth)
88  return nullopt;
89 
90  result *= 10;
91 
92  auto last = result;
93 
94  result += c - '0';
95  if (result < last)
96  return nullopt;
97 #endif
98  if /*constexpr*/ (count_magnitude)
99  *magnitude *= NumberType{ 10 };
100  }
101 
102  return result;
103 }
104 
105 /* Parse a signed exponent specifier.
106  * May start with a leading sign ('+' or '-'). The exponent value is limited to
107  * the range of int. The used range with a long double conversion is usually in
108  * the range -5000 to 5000, so this is not really a limitation.
109  */
110 constexpr optional<int> parse_exponent(string_view str) noexcept
111 {
112  bool negative = false;
113 
114  switch (str.front())
115  {
116  case '+':
117  str.remove_prefix(1);
118  break;
119  case '-':
120  str.remove_prefix(1);
121  negative = true;
122  break;
123  default:
124  break;
125  }
126 
127  auto opt_exp = to_unsigned_integer<int>(str);
128 
129  if (!opt_exp)
130  return nullopt;
131 
132  if (negative)
133  return -*opt_exp;
134  return *opt_exp;
135 }
136 
137 // For some 'long double' types with a big mantissa uint64 is not large enough.
138 // We resort to __uint128, which is a non standard extension in GCC and clang.
139 // But only if we need to.
140 // Note that on some compilers there are no std::numeric_limits<> for the extension
141 // type, and then asserts later on will fail. But usually that compilers have small
142 // long double types.
143 template <typename NumberType>
144 using FloatConversionIntType =
145  typename std::conditional<
146  (std::numeric_limits<std::uint64_t>::digits10 >= std::numeric_limits<NumberType>::digits10),
147  std::uint64_t,
148  #ifdef __SIZEOF_INT128__ // GCC, clang, intel
149  __uint128_t
150  #else
151  std::uint64_t
152  #endif
153  >::type;
154 
171 template <typename NumberType>
172 constexpr inline gul14::optional<NumberType> to_normalized_float(gul14::string_view i1, gul14::string_view i2) noexcept
173 {
174  static_assert(std::numeric_limits<FloatConversionIntType<NumberType>>::digits10
175  >= std::numeric_limits<NumberType>::digits10,
176  "FloatConversionIntType is too small for NumberType");
177 
178  i1 = i1.substr(0, std::min(i1.length(),
179  size_t(std::numeric_limits<FloatConversionIntType<NumberType>>::digits10)));
180  i2 = i2.substr(0, std::min(i2.length(),
181  size_t(std::numeric_limits<FloatConversionIntType<NumberType>>::digits10) - i1.length()));
182 
183  FloatConversionIntType<NumberType> accu{ 0 };
184 
185  auto magnitude = FloatConversionIntType<NumberType>{ 1 };
186 
187  if (not i2.empty()) {
188  auto f2 = to_unsigned_integer<FloatConversionIntType<NumberType>, true>(i2, &magnitude);
189  if (not f2.has_value())
190  return nullopt;
191  accu = *f2;
192  }
193  if (not i1.empty()) {
194  auto i2_magnitude = magnitude;
195  auto f1 = to_unsigned_integer<FloatConversionIntType<NumberType>, true>(i1, &magnitude);
196  if (not f1.has_value())
197  return nullopt;
198  accu += (*f1 * i2_magnitude);
199  }
200 
201  return static_cast<NumberType>(accu) / static_cast<NumberType>(magnitude / 10); // NOLINT(bugprone-integer-division): Precision loss is not possible with normalized accu
202 }
203 
204 template <typename NumberType>
205 struct ParseInfNanResult {
206  bool result_valid;
207  optional<NumberType> result;
208 };
209 
229 template <typename NumberType>
230 constexpr inline ParseInfNanResult<NumberType> parse_inf_nan(gul14::string_view str) noexcept
231 {
232  auto const strlength = str.length();
233  if (strlength == 0)
234  return { true, {} };
235 
236  if (gul14::starts_with_nocase(str, "inf")) {
237  if (strlength == 3 /* strlen("inf") */ )
238  return { true, make_optional(std::numeric_limits<NumberType>::infinity()) };
239  if (strlength == 8 /* strlen("infinity") */
240  and gul14::starts_with_nocase(str.substr(3), "inity"))
241  return { true, make_optional(std::numeric_limits<NumberType>::infinity()) };
242  return { true, {} };
243  }
244 
245  if (gul14::starts_with_nocase(str, "nan")) {
246  if (strlength == 3 /* strlen("nan") */ )
247  return { true, make_optional(std::numeric_limits<NumberType>::quiet_NaN()) };
248  if (strlength < 5 /* strlen("nan()") */ or str[3] != '(' or str.back() != ')')
249  return { true, {} };
250  str.remove_prefix(4);
251  str.remove_suffix(1);
252  while (str.length()) {
253  if (not is_nan_specifier(str.front()))
254  return { true, {} };
255  str.remove_prefix(1);
256  }
257  // We do not use the NaN specifier
258  return { true, make_optional(std::numeric_limits<NumberType>::quiet_NaN()) };
259  }
260  return { false, {} };
261 }
262 
280 template <typename NumberType>
281 constexpr inline optional<NumberType> to_unsigned_float(gul14::string_view str) noexcept
282 {
283  auto inf_nan = parse_inf_nan<NumberType>(str);
284  if (inf_nan.result_valid)
285  return inf_nan.result;
286 
287  int exponent = 0;
288  auto e_pos = str.find_first_of("eE");
289  if (e_pos != gul14::string_view::npos)
290  {
291  if (e_pos + 1 == str.size())
292  return nullopt;
293 
294  auto str_exponent = str.substr(e_pos + 1);
295 
296  str = str.substr(0, e_pos);
297 
298  auto opt_exp = detail::parse_exponent(str_exponent);
299 
300  if (!opt_exp)
301  return nullopt;
302 
303  exponent = *opt_exp;
304  }
305 
306  gul14::string_view str_before_point{ str };
307  gul14::string_view str_after_point;
308 
309  auto point_pos = str.find('.');
310  if (point_pos != gul14::string_view::npos)
311  {
312  str_before_point = str.substr(0, point_pos);
313  str_after_point = str.substr(point_pos + 1);
314  }
315 
316  if (str_before_point.empty() && str_after_point.empty())
317  return nullopt;
318 
319  // Get rid of leading zeros
320  while (!str_before_point.empty() and str_before_point[0] == '0')
321  str_before_point.remove_prefix(1);
322 
323  // Normalize the number
324  if (str_before_point.empty()) {
325  auto const old_digits = str_after_point.length();
326  while (!str_after_point.empty() and str_after_point[0] == '0')
327  str_after_point.remove_prefix(1);
328 
329  if (str_after_point.empty())
330  return { 0 };
331 
332  str_before_point = str_after_point.substr(0, 1);
333  str_after_point.remove_prefix(1);
334  exponent -= static_cast<int>(old_digits - str_after_point.length());
335  } else {
336  exponent += static_cast<int>(str_before_point.length() - 1);
337  }
338 
339  // Now the incoming number string is like this:
340  // "s.tr_before_point" "str_after_point" E exponent
341  // ^ ^
342  // | here is the decimal dot, virtually | corrected exponent
343 
344  using long_double = long double;
345  using CalcType = std::conditional_t<
346  std::greater<>()(sizeof(NumberType), sizeof(double)),
347  long_double, double>;
348 
349  auto norm_val = to_normalized_float<CalcType>(str_before_point, str_after_point);
350  if (not norm_val.has_value())
351  return nullopt;
352 
353  return NumberType(std::pow(CalcType(10), CalcType(exponent)) * *norm_val);
354 }
355 
373 template <typename NumberType>
374 inline optional<NumberType> strtold_wrapper(gul14::string_view str) noexcept
375 {
376  if (str.empty())
377  return nullopt;
378 
379  try
380  {
381  auto input = std::string{ str };
382  char* process_end;
383  auto value = static_cast<NumberType>(std::strtold(input.c_str(), &process_end));
384 
385  if (input.data() + input.size() != process_end) // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic): Pointer arithmetic needed because strtold gives pointer back
386  return nullopt;
387  return value;
388  }
389  catch (const std::exception &)
390  {
391  return nullopt;
392  }
393 }
394 
395 } // namespace detail
397 
398 
469 // Overload for unsigned integer types.
470 template <typename NumberType,
471  std::enable_if_t<std::is_integral<NumberType>::value &&
472  std::is_unsigned<NumberType>::value, int> = 0>
473 constexpr inline optional<NumberType> to_number(gul14::string_view str) noexcept
474 {
475  return detail::to_unsigned_integer<NumberType>(str);
476 }
477 
478 // Overload for signed integer types.
479 template <typename NumberType,
480  std::enable_if_t<std::is_integral<NumberType>::value &&
481  std::is_signed<NumberType>::value, int> = 0>
482 constexpr inline optional<NumberType> to_number(gul14::string_view str) noexcept
483 {
484  if (str.empty())
485  return nullopt;
486 
487  if (str.front() == '-')
488  {
489  using UnsignedT = std::make_unsigned_t<NumberType>;
490  constexpr auto max_abs_negative_value =
491  static_cast<UnsignedT>(std::numeric_limits<NumberType>::max()) + 1;
492 
493  str.remove_prefix(1);
494 
495  auto result = detail::to_unsigned_integer<UnsignedT>(str);
496  if (!result)
497  return nullopt;
498 
499  if (*result == max_abs_negative_value)
500  return std::numeric_limits<NumberType>::lowest();
501  else if (*result > max_abs_negative_value)
502  return nullopt;
503 
504  return static_cast<NumberType>(-static_cast<NumberType>(*result));
505  }
506 
507  return detail::to_unsigned_integer<NumberType>(str);
508 }
509 
510 // Overload for floating-point types.
511 template <typename NumberType,
512  std::enable_if_t<std::is_floating_point<NumberType>::value, int> = 0>
513 constexpr inline optional<NumberType> to_number(gul14::string_view str) noexcept
514 {
515  if (str.empty())
516  return nullopt;
517 
518  if (
519 #ifdef __APPLE__
520  // Apple clang 8.0.0 has a bug with std::pow and long double types,
521  // where the result is off by a huge amount. Use std::strtold() here.
522  (sizeof(NumberType) > sizeof(double)) ||
523 #endif
524 #ifdef _MSC_VER
525 # pragma warning( push )
526 # pragma warning( disable: 4127 ) // conditional expression is constant
527 #endif
528  (std::numeric_limits<detail::FloatConversionIntType<NumberType>>::digits10
529  <= std::numeric_limits<NumberType>::digits10)) {
530 #ifdef _MSC_VER
531 # pragma warning( pop )
532 #endif
533  // Too big for our approach. Resort to non-constexpr functionality.
534  // This actually never happenes with the currently supported platforms / compilers.
535  // (Except long double on Darwin)
536  return detail::strtold_wrapper<NumberType>(str);
537  }
538 
539  if (str.front() == '-')
540  {
541  str.remove_prefix(1);
542  auto result = detail::to_unsigned_float<NumberType>(str);
543  if (!result)
544  return nullopt;
545  return -*result;
546  }
547 
548  return detail::to_unsigned_float<NumberType>(str);
549 }
550 
551 } // namespace gul14
552 
553 #endif
554 
555 // vi:ts=4:sw=4:et:sts=4
A class template that can either contain a value of a certain type or not.
Definition: optional.h:268
Definition of macros used internally by GUL.
Namespace gul14 contains all functions and classes of the General Utility Library.
Definition: doxygen.h:26
constexpr optional< NumberType > to_number(gul14::string_view str) noexcept
Convert an ASCII string_view into a number.
Definition: to_number.h:473
basic_string_view< char > string_view
A view to a contiguous sequence of chars.
Definition: string_view.h:623
constexpr bool starts_with_nocase(string_view str, string_view prefix) noexcept
Determine whether a string starts with another string.
Definition: substring_checks.h:307
Implementation of gul14::optional.
Provides a gul14::string_view that is fully compatible with C++17's std::string_view.
Definition of contains(), ends_with(), and starts_with().