1#include <IO/ReadHelpers.h>
2
3
4namespace DB
5{
6
7namespace ErrorCodes
8{
9 extern const int CANNOT_PARSE_NUMBER;
10 extern const int ARGUMENT_OUT_OF_BOUND;
11}
12
13
14template <bool _throw_on_error, typename T>
15inline bool readDigits(ReadBuffer & buf, T & x, unsigned int & digits, int & exponent, bool digits_only = false)
16{
17 x = 0;
18 exponent = 0;
19 unsigned int max_digits = digits;
20 digits = 0;
21 unsigned int places = 0;
22 typename T::NativeType sign = 1;
23 bool leading_zeroes = true;
24 bool after_point = false;
25
26 if (buf.eof())
27 {
28 if constexpr (_throw_on_error)
29 throwReadAfterEOF();
30 return false;
31 }
32
33 if (!buf.eof())
34 {
35 switch (*buf.position())
36 {
37 case '-':
38 sign = -1;
39 [[fallthrough]];
40 case '+':
41 ++buf.position();
42 break;
43 }
44 }
45
46 bool stop = false;
47 while (!buf.eof() && !stop)
48 {
49 const char & byte = *buf.position();
50 switch (byte)
51 {
52 case '.':
53 after_point = true;
54 leading_zeroes = false;
55 break;
56 case '0':
57 {
58 if (leading_zeroes)
59 break;
60
61 if (after_point)
62 {
63 ++places; /// Count trailing zeroes. They would be used only if there's some other digit after them.
64 break;
65 }
66 [[fallthrough]];
67 }
68 case '1': [[fallthrough]];
69 case '2': [[fallthrough]];
70 case '3': [[fallthrough]];
71 case '4': [[fallthrough]];
72 case '5': [[fallthrough]];
73 case '6': [[fallthrough]];
74 case '7': [[fallthrough]];
75 case '8': [[fallthrough]];
76 case '9':
77 {
78 leading_zeroes = false;
79
80 ++places; // num zeroes before + current digit
81 if (digits + places > max_digits)
82 {
83 if constexpr (_throw_on_error)
84 throw Exception("Too many digits (" + std::to_string(digits + places) + " > " + std::to_string(max_digits)
85 + ") in decimal value", ErrorCodes::ARGUMENT_OUT_OF_BOUND);
86 return false;
87 }
88
89 digits += places;
90 if (after_point)
91 exponent -= places;
92
93 // TODO: accurate shift10 for big integers
94 for (; places; --places)
95 x *= 10;
96 x += (byte - '0');
97 break;
98 }
99 case 'e': [[fallthrough]];
100 case 'E':
101 {
102 ++buf.position();
103 Int32 addition_exp = 0;
104 readIntText(addition_exp, buf);
105 exponent += addition_exp;
106 stop = true;
107 continue;
108 }
109
110 default:
111 if (digits_only)
112 {
113 if constexpr (_throw_on_error)
114 throw Exception("Unexpected symbol while reading decimal", ErrorCodes::CANNOT_PARSE_NUMBER);
115 return false;
116 }
117 stop = true;
118 continue;
119 }
120 ++buf.position();
121 }
122
123 x *= sign;
124 return true;
125}
126
127template <typename T>
128inline void readDecimalText(ReadBuffer & buf, T & x, unsigned int precision, unsigned int & scale, bool digits_only = false)
129{
130 unsigned int digits = precision;
131 int exponent;
132 readDigits<true>(buf, x, digits, exponent, digits_only);
133
134 if (static_cast<int>(digits) + exponent > static_cast<int>(precision - scale))
135 throw Exception("Decimal value is too big", ErrorCodes::ARGUMENT_OUT_OF_BOUND);
136 if (static_cast<int>(scale) + exponent < 0)
137 throw Exception("Decimal value is too small", ErrorCodes::ARGUMENT_OUT_OF_BOUND);
138
139 scale += exponent;
140}
141
142template <typename T>
143inline bool tryReadDecimalText(ReadBuffer & buf, T & x, unsigned int precision, unsigned int & scale)
144{
145 unsigned int digits = precision;
146 int exponent;
147
148 if (!readDigits<false>(buf, x, digits, exponent, true) ||
149 static_cast<int>(digits) + exponent > static_cast<int>(precision - scale) ||
150 static_cast<int>(scale) + exponent < 0)
151 return false;
152
153 scale += exponent;
154 return true;
155}
156
157template <typename T>
158inline void readCSVDecimalText(ReadBuffer & buf, T & x, unsigned int precision, unsigned int & scale)
159{
160 if (buf.eof())
161 throwReadAfterEOF();
162
163 char maybe_quote = *buf.position();
164
165 if (maybe_quote == '\'' || maybe_quote == '\"')
166 ++buf.position();
167
168 readDecimalText(buf, x, precision, scale, false);
169
170 if (maybe_quote == '\'' || maybe_quote == '\"')
171 assertChar(maybe_quote, buf);
172}
173
174}
175