All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
NumberParsing.h
Go to the documentation of this file.
1 #ifndef NUMBER_PARSING_H
2 #define NUMBER_PARSING_H
3 
4 #include <algorithm>
5 #include <cassert>
6 #include <cctype>
7 #include <tuple>
8 #include <limits>
9 #include <stdexcept>
10 #include <type_traits>
11 
12 
13 #include "Enforce.h"
14 
15 namespace Aux {
16 namespace Parsing {
17 
18 
19 namespace Impl {
20 
21  template<typename CharIterator>
22  std::tuple<CharIterator, char> dropSpaces(CharIterator it, CharIterator end);
23 
24  template<typename Integer>
25  double powerOf10(Integer exp);
26 
27  class IntegerTag{};
28 
29  template<typename Integer, typename CharIterator, typename ValidationPolicy>
30  std::tuple<Integer, CharIterator> strTo(CharIterator it, CharIterator end,
31  IntegerTag);
32 
33  class RealTag{};
34 
35  template<typename Real, typename CharIterator, typename ValidationPolicy>
36  std::tuple<Real, CharIterator> strTo(CharIterator it, CharIterator end, RealTag);
37 
38  template<typename T>
39  using ArithmeticTag = typename std::conditional<
40  std::is_integral<T>::value,
41  IntegerTag,
42  typename std::conditional<std::is_floating_point<T>::value,
43  RealTag, void>::type
44  >::type;
45 
46 } // namespace Impl
47 
48 
66 template<typename Number, typename CharIterator, typename ValidationPolicy = Checkers::Asserter>
67 std::tuple<Number, CharIterator> strTo(CharIterator it, CharIterator end) {
68  return Impl::strTo<Number, CharIterator, ValidationPolicy>(it, end, Impl::ArithmeticTag<Number>{});
69 }
70 
71 
72 
73 namespace Impl {
74 
75 template<typename Integer, typename CharIterator, typename ValidationPolicy>
76 std::tuple<Integer, CharIterator> strTo(CharIterator it, const CharIterator end, IntegerTag) {
77  using Impl::dropSpaces;
78  ValidationPolicy::enforce(it != end);
79 
80  char c = *it;
81  std::tie(it, c) = dropSpaces(it, end);
82 
83  bool isNegative = false;
84  if(std::is_signed<Integer>::value) { // this should be optimized away entirely
85  switch (c) {
86  case '-':
87  isNegative = true;
88  // fallthrough
89  case '+':
90  ++it;
91  if (it == end) {
92  throw std::invalid_argument{
93  "string contains no digits after sign"};
94  }
95  c = *it;
96  break;
97  default:
98  break;
99  }
100  }
101 
102  if(!isdigit(c)) {
103  throw std::invalid_argument{"string contains no digits"};
104  }
105 
106  Integer val = 0;
107  while (true) {
108  ValidationPolicy::enforce(std::numeric_limits<Integer>::max() / 10 >= val);
109 
110  val *= 10;
111 
112  c -= '0';
113  ValidationPolicy::enforce(std::numeric_limits<Integer>::max() - c >= val);
114 
115  val += c;
116 
117  ++it;
118  if(it == end) {
119  break;
120  }
121  c = *it;
122  if(!isdigit(c)) {
123  break;
124  }
125  }
126 
127  if(isNegative) {
128  val = -val;
129  }
130 
131  std::tie(it, c) = dropSpaces(it, end);
132 
133  return std::make_tuple(val, it);
134 }
135 
136 template<typename Real, typename CharIterator, typename ValidationPolicy>
137 std::tuple<Real, CharIterator> strTo(CharIterator it, const CharIterator end, RealTag) {
138 
139  static_assert(std::numeric_limits<Real>::max_digits10
140  <= std::numeric_limits<std::uintmax_t>::digits10,
141  "can't safe mantissa in biggest integer");
142 
143  using Impl::dropSpaces;
144  using Impl::powerOf10;
145 
146  char c;
147  bool isNegative = false;
148  std::uintmax_t mantissa = 0;
149  int exp = 0;
150 
151  auto makeReturnValue = [&]() {
152  Real fp_mantissa = mantissa;
153  Real value = fp_mantissa * powerOf10(exp);
154  if (isNegative) {
155  value = -value;
156  }
157  return std::make_tuple(value, it);
158  };
159 
160  // drop whitespace:
161  std::tie(it, c) = dropSpaces(it, end);
162 
163  ValidationPolicy::enforce(it != end);
164 
165  // set sign:
166  switch (c) {
167  case '-':
168  isNegative = true;
169  // fallthrough
170  case '+':
171  ++it;
172  if (it == end) {
173  throw std::invalid_argument{"string contains no digits"};
174  }
175  c = *it;
176  break;
177  default:
178  break;
179  }
180 
181  // number of decimal digits that can be stored in the mantissa and the used integer
182  unsigned remainingDigits = std::numeric_limits<Real>::max_digits10;
183 
184  //read 'big' part of the mantissa:
185  while (remainingDigits > 0) {
186  if (!isdigit(c)) {
187  break;
188  }
189  --remainingDigits;
190  mantissa *= 10;
191  mantissa += c - '0';
192  ++it;
193  if (it == end) {
194  return makeReturnValue();
195  }
196  c = *it;
197  }
198  if(remainingDigits == 0) {
199  if(isdigit(c)) {
200  //round correctly:
201  if (c - '0' >= 5) {
202  ++mantissa;
203  }
204  }
205  while (isdigit(c)) {
206  ++exp;
207  ++it;
208  if (it == end) {
209  break;
210  }
211  c = *it;
212  }
213  }
214  // read 'small' part of the mantissa
215  if (c == '.') {
216  ++it;
217  if(it == end) {
218  return makeReturnValue();
219  }
220  c = *it;
221  while (remainingDigits > 0) {
222  if (!isdigit(c)) {
223  break;
224  }
225  --exp;
226  --remainingDigits;
227  mantissa *= 10;
228  mantissa += c - '0';
229  ++it;
230  if (it == end) {
231  return makeReturnValue();
232  }
233  c = *it;
234  }
235  if (isdigit(c)) {
236  //round correctly:
237  if (c - '0' >= 5) {
238  ++mantissa;
239  }
240  }
241  // skip the remaining digits:
242  it = std::find_if_not(it, end, isdigit);
243  if (it == end) {
244  return makeReturnValue();
245  }
246  c = *it;
247  }
248 
249  // calculate the final exponent:
250  if (c == 'e' || c == 'E') {
251  ++it;
252  int tmp;
253  // we need to pass IntegerTag explicitly here because we are in a
254  // nested namespace. This shouldn't be required anywhere else
255  std::tie(tmp, it) = strTo<int, CharIterator, ValidationPolicy>(it, end, IntegerTag{});
256  exp += tmp;
257  }
258 
259  // drop further whitespace:
260  std::tie(it, c) = dropSpaces(it, end);
261 
262  return makeReturnValue();
263 }
264 
265 template<typename CharIterator>
266 std::tuple<CharIterator, char> dropSpaces(CharIterator it, CharIterator end) {
267  char c = 0;
268  while(it != end && std::isspace((c = *it))) {
269  ++it;
270  }
271  return std::make_tuple(it, c);
272 }
273 
274 template<typename Integer>
275 double powerOf10(Integer exp) {
276  if (exp < 0) {
277  return 1.0 / powerOf10(-exp);
278  }
279  switch(exp) {
280  case 0: return 1;
281  case 1: return 10;
282  case 2: return 100;
283  case 3: return 1000;
284  case 4: return 10000;
285  case 5: return 100000;
286  case 6: return 1000000;
287  case 7: return 10000000;
288  case 8: return 100000000;
289  case 9: return 1000000000;
290  default:
291  auto tmp = powerOf10(exp / 2);
292  if (exp % 2 == 0) {
293  return tmp * tmp;
294  } else {
295  return tmp * tmp * 10;
296  }
297  }
298 }
299 
300 } // namespace Impl
301 
302 }} // namespace Aux::Parsing
303 
304 #endif
double powerOf10(Integer exp)
Definition: NumberParsing.h:275
std::tuple< Integer, CharIterator > strTo(CharIterator it, CharIterator end, IntegerTag)
Definition: NumberParsing.h:76
std::tuple< CharIterator, char > dropSpaces(CharIterator it, CharIterator end)
Definition: NumberParsing.h:266
Definition: NumberParsing.h:27
Definition: NumberParsing.h:33
typename std::conditional< std::is_integral< T >::value, IntegerTag, typename std::conditional< std::is_floating_point< T >::value, RealTag, void >::type >::type ArithmeticTag
Definition: NumberParsing.h:44
std::tuple< Number, CharIterator > strTo(CharIterator it, CharIterator end)
Parses a range of characters as number.
Definition: NumberParsing.h:67
void enforce(bool b, const char *msg="")
Enforces that b is true and throws an Exception otherwise.
Definition: Enforce.h:17