General Utility Library for C++14  2.9
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 
48 namespace detail {
49 
50 constexpr inline bool is_digit(char c) noexcept
51 {
52  return c >= '0' && c <= '9';
53 }
54 
55 constexpr 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 
68 template <typename NumberType, bool count_magnitude = false>
69 constexpr inline optional<NumberType> to_unsigned_integer(gul14::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 nullopt;
78 
79  NumberType result = 0;
80 
81  for (char c : str)
82  {
83  if (!is_digit(c))
84  return nullopt;
85 
86 #ifdef __GNUC__
87  if (__builtin_mul_overflow(result, NumberType{ 10 }, &result)) // NOLINT(cppcoreguidelines-pro-type-vararg)
88  return nullopt;
89 
90  if (__builtin_add_overflow(result, static_cast<NumberType>(c - '0'), &result)) // NOLINT(cppcoreguidelines-pro-type-vararg)
91  return nullopt;
92 #else
93  if (result > max_tenth)
94  return nullopt;
95 
96  result *= 10;
97 
98  auto last = result;
99 
100  result += c - '0';
101  if (result < last)
102  return 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  */
116 constexpr optional<int> parse_exponent(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 
133  auto opt_exp = to_unsigned_integer<int>(str);
134 
135  if (!opt_exp)
136  return 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.
149 template <typename NumberType>
150 using FloatConversionIntType =
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
155  __uint128_t
156  #else
157  std::uint64_t
158  #endif
159  >::type;
160 
177 template <typename NumberType>
178 constexpr inline gul14::optional<NumberType> to_normalized_float(gul14::string_view i1, gul14::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 
189  FloatConversionIntType<NumberType> accu{ 0 };
190 
191  auto magnitude = FloatConversionIntType<NumberType>{ 1 };
192 
193  if (not i2.empty()) {
194  auto f2 = to_unsigned_integer<FloatConversionIntType<NumberType>, true>(i2, &magnitude);
195  if (not f2.has_value())
196  return nullopt;
197  accu = *f2;
198  }
199  if (not i1.empty()) {
200  auto i2_magnitude = magnitude;
201  auto f1 = to_unsigned_integer<FloatConversionIntType<NumberType>, true>(i1, &magnitude);
202  if (not f1.has_value())
203  return 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 
210 template <typename NumberType>
211 struct ParseInfNanResult {
212  bool result_valid;
213  optional<NumberType> result;
214 };
215 
235 template <typename NumberType>
236 constexpr inline ParseInfNanResult<NumberType> parse_inf_nan(gul14::string_view str) noexcept
237 {
238  auto const strlength = str.length();
239  if (strlength == 0)
240  return { true, {} };
241 
242  if (gul14::starts_with_nocase(str, "inf")) {
243  if (strlength == 3 /* strlen("inf") */ )
244  return { true, make_optional(std::numeric_limits<NumberType>::infinity()) };
245  if (strlength == 8 /* strlen("infinity") */
246  and gul14::starts_with_nocase(str.substr(3), "inity"))
247  return { true, make_optional(std::numeric_limits<NumberType>::infinity()) };
248  return { true, {} };
249  }
250 
251  if (gul14::starts_with_nocase(str, "nan")) {
252  if (strlength == 3 /* strlen("nan") */ )
253  return { true, 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, make_optional(std::numeric_limits<NumberType>::quiet_NaN()) };
265  }
266  return { false, {} };
267 }
268 
286 template <typename NumberType>
287 constexpr inline optional<NumberType> to_unsigned_float(gul14::string_view str) noexcept
288 {
289  auto inf_nan = parse_inf_nan<NumberType>(str);
290  if (inf_nan.result_valid)
291  return inf_nan.result;
292 
293  int exponent = 0;
294  auto e_pos = str.find_first_of("eE");
295  if (e_pos != gul14::string_view::npos)
296  {
297  if (e_pos + 1 == str.size())
298  return nullopt;
299 
300  auto str_exponent = str.substr(e_pos + 1);
301 
302  str = str.substr(0, e_pos);
303 
304  auto opt_exp = detail::parse_exponent(str_exponent);
305 
306  if (!opt_exp)
307  return nullopt;
308 
309  exponent = *opt_exp;
310  }
311 
312  gul14::string_view str_before_point{ str };
313  gul14::string_view str_after_point;
314 
315  auto point_pos = str.find('.');
316  if (point_pos != gul14::string_view::npos)
317  {
318  str_before_point = str.substr(0, point_pos);
319  str_after_point = str.substr(point_pos + 1);
320  }
321 
322  if (str_before_point.empty() && str_after_point.empty())
323  return nullopt;
324 
325  // Get rid of leading zeros
326  while (!str_before_point.empty() and str_before_point[0] == '0')
327  str_before_point.remove_prefix(1);
328 
329  // Normalize the number
330  if (str_before_point.empty()) {
331  auto const old_digits = str_after_point.length();
332  while (!str_after_point.empty() and str_after_point[0] == '0')
333  str_after_point.remove_prefix(1);
334 
335  if (str_after_point.empty())
336  return { 0 };
337 
338  str_before_point = str_after_point.substr(0, 1);
339  str_after_point.remove_prefix(1);
340  exponent -= static_cast<int>(old_digits - str_after_point.length());
341  } else {
342  exponent += static_cast<int>(str_before_point.length() - 1);
343  }
344 
345  // Now the incoming number string is like this:
346  // "s.tr_before_point" "str_after_point" E exponent
347  // ^ ^
348  // | here is the decimal dot, virtually | corrected exponent
349 
350  using long_double = long double;
351  using CalcType = std::conditional_t<
352  std::greater<>()(sizeof(NumberType), sizeof(double)),
353  long_double, double>;
354 
355  auto norm_val = to_normalized_float<CalcType>(str_before_point, str_after_point);
356  if (not norm_val.has_value())
357  return nullopt;
358 
359  return NumberType(std::pow(CalcType(10), CalcType(exponent)) * *norm_val);
360 }
361 
379 template <typename NumberType>
380 inline optional<NumberType> strtold_wrapper(gul14::string_view str) noexcept
381 {
382  if (str.empty())
383  return nullopt;
384 
385  try
386  {
387  auto input = std::string{ str };
388  char* process_end;
389  auto value = static_cast<NumberType>(std::strtold(input.c_str(), &process_end));
390 
391  if (input.data() + input.size() != process_end) // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic): Pointer arithmetic needed because strtold gives pointer back
392  return nullopt;
393  return value;
394  }
395  catch (const std::exception &)
396  {
397  return nullopt;
398  }
399 }
400 
401 } // namespace detail
403 
404 
475 // Overload for unsigned integer types.
476 template <typename NumberType,
477  std::enable_if_t<std::is_integral<NumberType>::value &&
478  std::is_unsigned<NumberType>::value, int> = 0>
479 constexpr inline optional<NumberType> to_number(gul14::string_view str) noexcept
480 {
481  return detail::to_unsigned_integer<NumberType>(str);
482 }
483 
484 // Overload for signed integer types.
485 template <typename NumberType,
486  std::enable_if_t<std::is_integral<NumberType>::value &&
487  std::is_signed<NumberType>::value, int> = 0>
488 constexpr inline optional<NumberType> to_number(gul14::string_view str) noexcept
489 {
490  if (str.empty())
491  return nullopt;
492 
493  if (str.front() == '-')
494  {
495  using UnsignedT = std::make_unsigned_t<NumberType>;
496  constexpr auto max_abs_negative_value =
497  static_cast<UnsignedT>(std::numeric_limits<NumberType>::max()) + 1;
498 
499  str.remove_prefix(1);
500 
501  auto result = detail::to_unsigned_integer<UnsignedT>(str);
502  if (!result)
503  return nullopt;
504 
505  if (*result == max_abs_negative_value)
506  return std::numeric_limits<NumberType>::lowest();
507  else if (*result > max_abs_negative_value)
508  return nullopt;
509 
510  return static_cast<NumberType>(-static_cast<NumberType>(*result));
511  }
512 
513  return detail::to_unsigned_integer<NumberType>(str);
514 }
515 
516 // Overload for floating-point types.
517 template <typename NumberType,
518  std::enable_if_t<std::is_floating_point<NumberType>::value, int> = 0>
519 constexpr inline optional<NumberType> to_number(gul14::string_view str) noexcept
520 {
521  if (str.empty())
522  return nullopt;
523 
524  if (
525 #ifdef __APPLE__
526  // Apple clang 8.0.0 has a bug with std::pow and long double types,
527  // where the result is off by a huge amount. Use std::strtold() here.
528  (sizeof(NumberType) > sizeof(double)) ||
529 #endif
530 #ifdef _MSC_VER
531 # pragma warning( push )
532 # pragma warning( disable: 4127 ) // conditional expression is constant
533 #endif
534  (std::numeric_limits<detail::FloatConversionIntType<NumberType>>::digits10
535  <= std::numeric_limits<NumberType>::digits10)) {
536 #ifdef _MSC_VER
537 # pragma warning( pop )
538 #endif
539  // Too big for our approach. Resort to non-constexpr functionality.
540  // This actually never happenes with the currently supported platforms / compilers.
541  // (Except long double on Darwin)
542  return detail::strtold_wrapper<NumberType>(str);
543  }
544 
545  if (str.front() == '-')
546  {
547  str.remove_prefix(1);
548  auto result = detail::to_unsigned_float<NumberType>(str);
549  if (!result)
550  return nullopt;
551  return -*result;
552  }
553 
554  return detail::to_unsigned_float<NumberType>(str);
555 }
556 
558 
559 } // namespace gul14
560 
561 #endif
562 
563 // 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:274
basic_string_view< char > string_view
A view to a contiguous sequence of chars.
Definition: string_view.h:630
constexpr bool starts_with_nocase(string_view str, string_view prefix) noexcept
Determine whether a string starts with another string.
Definition: substring_checks.h:313
constexpr optional< NumberType > to_number(gul14::string_view str) noexcept
Convert an ASCII string_view into a number.
Definition: to_number.h:479
Definition of macros used internally by GUL.
Namespace gul14 contains all functions and classes of the General Utility Library.
Definition: doxygen.h:26
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().