File: numberparser.h

package info (click to toggle)
aoflagger 3.4.0-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 8,960 kB
  • sloc: cpp: 83,076; python: 10,187; sh: 260; makefile: 178
file content (158 lines) | stat: -rw-r--r-- 4,206 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#include <cmath>
#include <stdexcept>
#include <string>

#ifndef HAVE_EXP10
#define exp10(x) exp((2.3025850929940456840179914546844) * (x))
#endif

class NumberParsingException : public std::runtime_error {
 public:
  explicit NumberParsingException(const std::string& str)
      : std::runtime_error(std::string("Failed to parse number: ") + str) {}
};

/**
 * This class implements functions that copy the behaviour of atof and strtod,
 * etc. Unfortunately, atof are locale-dependent and e.g. a Dutch machine will
 * interpret a decimal point differently. This is undesirable, hence the
 * reimplementation here.
 * TODO can be replaced by std::from_chars() once C++17 is used.
 */
class NumberParser {
 public:
  /**
   * @throws NumberParsingException when @c str can not be parsed.
   */
  static double ToDouble(const char* str) { return toNumber<double>(str); }

  /**
   * @throws NumberParsingException when @c str can not be parsed.
   */
  static short ToShort(const char* str) { return toSignedInteger<short>(str); }

 private:
  /**
   * @throws NumberParsingException when @c str can not be parsed.
   */
  template <typename T>
  static T toNumber(const char* str) {
    if (*str == 0) throw NumberParsingException("Supplied string is empty");

    // Read optional sign
    bool isNegative;
    if (isNegativeSymbol(*str)) {
      isNegative = true;
      ++str;
    } else if (isPositiveSymbol(*str)) {
      isNegative = false;
      ++str;
    } else {
      isNegative = false;
    }

    // Read everything before decimal point
    if (!isDigit(*str))
      throw NumberParsingException(
          "Number did not start with digits after optional sign symbol");
    T val = (T)((*str) - '0');
    ++str;

    while (isDigit(*str)) {
      val = val * 10.0 + (T)((*str) - '0');
      ++str;
    }

    // Skip decimal point if given
    if (isDecimalPoint(*str)) ++str;

    // Read decimals
    T currentValue = 0.1;
    while (isDigit(*str)) {
      val += currentValue * (T)((*str) - '0');
      currentValue /= 10.0;
      ++str;
    }

    // Read optional exponent
    if (isExponentSymbol(*str)) {
      ++str;

      try {
        short e = ToShort(str);
        val = val * intPow10(e);
      } catch (NumberParsingException& e) {
        throw NumberParsingException("Could not parse exponent");
      }
    }
    // If there was an exponent, ToShort has checked for garbage at the end and
    // str is not updated, otherwise we still need to check that...
    else if (*str != 0)
      throw NumberParsingException(
          "The number contains invalid characters after its digits");

    if (isNegative)
      return -val;
    else
      return val;
  }

  /**
   * @throws NumberParsingException when @c str can not be parsed.
   */
  template <typename T>
  static T toSignedInteger(const char* str) {
    if (*str == 0) throw NumberParsingException("Supplied string is empty");

    // Read optional sign
    bool isNegative;
    if (isNegativeSymbol(*str)) {
      isNegative = true;
      ++str;
    } else if (isPositiveSymbol(*str)) {
      isNegative = false;
      ++str;
    } else {
      isNegative = false;
    }

    // Read digits
    if (!isDigit(*str))
      throw NumberParsingException(
          "Integer did not start with digits after optional sign symbol");
    T val = (T)((*str) - '0');
    ++str;

    while (isDigit(*str)) {
      val = val * 10 + (T)((*str) - '0');
      ++str;
    }

    // Check if str is completely read.
    if (*str == 0) {
      if (isNegative)
        return -val;
      else
        return val;
    } else {
      throw NumberParsingException(
          "The integer contains invalid characters after its digits");
    }
  }

  static double intPow10(int par) {
    // TODO this can be done a lot faster and more accurate with knowledge that
    // par = int.
    return exp10((double)par);
  }

  static bool isDigit(char c) { return c >= '0' && c <= '9'; }

  static bool isPositiveSymbol(char c) { return c == '+'; }

  static bool isNegativeSymbol(char c) { return c == '-'; }

  static bool isDecimalPoint(char c) { return c == '.'; }

  static bool isExponentSymbol(char c) { return c == 'e' || c == 'E'; }
};