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