Google Cloud Spanner C++ Client  1.32.0
A C++ Client Library for Google Cloud Spanner
numeric.cc
Go to the documentation of this file.
1 // Copyright 2020 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "google/cloud/spanner/numeric.h"
16 #include "google/cloud/status.h"
17 #include "absl/strings/string_view.h"
18 #include <algorithm>
19 #include <cctype>
20 #include <cerrno>
21 #include <cmath>
22 #include <cstddef>
23 #include <deque>
24 #include <iomanip>
25 #include <limits>
26 #include <locale>
27 #include <sstream>
28 #include <string>
29 
30 namespace google {
31 namespace cloud {
32 namespace spanner_internal {
33 inline namespace SPANNER_CLIENT_NS {
34 
35 namespace {
36 
37 inline bool IsDigit(char ch) {
38  return std::isdigit(static_cast<unsigned char>(ch)) != 0;
39 }
40 
41 inline bool IsSpace(char ch) {
42  return std::isspace(static_cast<unsigned char>(ch)) != 0;
43 }
44 
45 Status InvalidArgument(std::string message) {
46  return Status(StatusCode::kInvalidArgument, std::move(message));
47 }
48 
49 Status OutOfRange(std::string message) {
50  return Status(StatusCode::kOutOfRange, std::move(message));
51 }
52 
53 // Do the pieces form a canonical, in-range value, with no rounding required?
54 bool IsCanonical(absl::string_view sign_part, absl::string_view int_part,
55  absl::string_view frac_part) {
56  if (int_part.empty()) return false;
57  if (int_part.size() > spanner::Numeric::kIntPrec) return false;
58  if (frac_part.size() > 1 + spanner::Numeric::kFracPrec) return false;
59  if (int_part.size() == 1 && int_part.front() == '0') {
60  if (frac_part.empty()) { // Should match "0".
61  if (!sign_part.empty()) return false;
62  } else { // Should match "-?0.[0-9]*[1-9]".
63  if (!sign_part.empty() && sign_part.front() == '+') return false;
64  if (frac_part.size() == 1 || frac_part.back() == '0') return false;
65  }
66  } else { // Should match "-?[1-9][0-9]*(.[0-9]*[1-9])?".
67  if (!sign_part.empty() && sign_part.front() == '+') return false;
68  if (int_part.front() == '0') return false;
69  if (frac_part.size() == 1) return false;
70  if (!frac_part.empty() && frac_part.back() == '0') return false;
71  }
72  return true;
73 }
74 
75 // Round the value to `prec` digits after the decimal point, with halfway
76 // cases rounding away from zero.
77 void Round(std::deque<char>& int_rep, std::deque<char>& frac_rep,
78  std::size_t prec) {
79  static constexpr auto kDigits = "0123456789";
80 
81  auto it = frac_rep.begin() + (std::min)(prec, frac_rep.size());
82  if (frac_rep.size() <= prec || std::strchr(kDigits, *it) - kDigits < 5) {
83  // Round towards zero.
84  while (it != frac_rep.begin() && *(it - 1) == '0') --it;
85  frac_rep.erase(it, frac_rep.end());
86  return;
87  }
88 
89  // Round away from zero (requires add and carry).
90  while (it != frac_rep.begin()) {
91  if (*--it != '9') {
92  *it = *(std::strchr(kDigits, *it) + 1);
93  frac_rep.erase(++it, frac_rep.end());
94  return;
95  }
96  }
97 
98  // Carry into the integer part.
99  frac_rep.clear();
100  int_rep.push_front('0');
101  it = int_rep.end();
102  while (*--it == '9') *it = '0';
103  *it = *(std::strchr(kDigits, *it) + 1);
104 }
105 
106 } // namespace
107 
108 std::string ToString(absl::int128 value) {
109  std::ostringstream ss;
110  ss << value;
111  return std::move(ss).str();
112 }
113 
114 std::string ToString(absl::uint128 value) {
115  std::ostringstream ss;
116  ss << value;
117  return std::move(ss).str();
118 }
119 
120 Status DataLoss(std::string message) {
121  return Status(StatusCode::kDataLoss, std::move(message));
122 }
123 
124 // Succeeds if `s` matches either of these regular expressions ...
125 //
126 // [-+]?[0-9]+(.[0-9]*)?([eE][-+]?[0-9]+)?
127 // [-+]?.[0-9]+([eE][-+]?[0-9]+)?
128 //
129 // and the value is within the allowed range, producing a representation
130 // that matches one of these regular expressions ...
131 //
132 // 0 // value == 0
133 // -?0.[0-9]{0,8}[1-9] // 0 < abs(value) < 1
134 // -?[1-9][0-9]{0,28}(.[0-9]{0,8}[1-9])? // abs(value) >= 1
135 //
136 // where the fractional part has been rounded to `kFracPrec` decimal places.
137 StatusOr<spanner::Numeric> MakeNumeric(std::string s) {
138  char const* p = s.c_str();
139  char const* e = p + s.size();
140 
141  // Consume any sign part.
142  auto sign_part = absl::string_view(p, 0);
143  if (p != e && (*p == '+' || *p == '-')) {
144  sign_part = absl::string_view(p++, 1);
145  }
146 
147  // Consume any integral part.
148  char const* ip = p;
149  for (; p != e && IsDigit(*p); ++p) continue;
150  auto int_part = absl::string_view(ip, p - ip);
151 
152  // Consume any fractional part.
153  auto frac_part = absl::string_view(p, 0);
154  if (p != e && *p == '.') {
155  char const* fp = p++;
156  for (; p != e && IsDigit(*p); ++p) continue;
157  frac_part = absl::string_view(fp, p - fp);
158  }
159 
160  // This is the expected case, and avoids any allocations.
161  if (p == e && IsCanonical(sign_part, int_part, frac_part)) {
162  return spanner::Numeric(std::move(s));
163  }
164 
165  // Consume any exponent part.
166  long exponent = 0; // NOLINT(google-runtime-int)
167  if (p != e && (*p == 'e' || *p == 'E')) {
168  if (p + 1 != e && !IsSpace(*(p + 1))) {
169  errno = 0;
170  char* ep = nullptr;
171  exponent = std::strtol(p + 1, &ep, 10);
172  if (ep != p + 1) {
173  if (errno != 0) return OutOfRange(std::move(s));
174  p = ep;
175  }
176  }
177  }
178 
179  // That must have consumed everything.
180  if (p != e) return InvalidArgument(std::move(s));
181 
182  // There must be at least one digit.
183  if (int_part.empty() && frac_part.size() <= 1) {
184  return InvalidArgument(std::move(s));
185  }
186 
187  auto int_rep = std::deque<char>(int_part.begin(), int_part.end());
188  auto frac_rep = std::deque<char>(frac_part.begin(), frac_part.end());
189  if (!frac_rep.empty()) frac_rep.pop_front(); // remove the decimal point
190 
191  // Symbolically multiply "int_rep.frac_rep" by 10^exponent.
192  if (exponent >= 0) {
193  auto shift = std::min<std::size_t>(exponent, frac_rep.size());
194  int_rep.insert(int_rep.end(), frac_rep.begin(), frac_rep.begin() + shift);
195  int_rep.insert(int_rep.end(), exponent - shift, '0');
196  frac_rep.erase(frac_rep.begin(), frac_rep.begin() + shift);
197  } else {
198  auto shift = std::min<std::size_t>(-exponent, int_rep.size());
199  frac_rep.insert(frac_rep.begin(), int_rep.end() - shift, int_rep.end());
200  frac_rep.insert(frac_rep.begin(), -exponent - shift, '0');
201  int_rep.erase(int_rep.end() - shift, int_rep.end());
202  }
203 
204  // Round/canonicalize the fractional part.
205  Round(int_rep, frac_rep, spanner::Numeric::kFracPrec);
206 
207  // Canonicalize and range check the integer part.
208  while (!int_rep.empty() && int_rep.front() == '0') int_rep.pop_front();
209  if (int_rep.size() > spanner::Numeric::kIntPrec) {
210  return OutOfRange(std::move(s));
211  }
212 
213  // Add any sign and decimal point.
214  bool empty = int_rep.empty() && frac_rep.empty();
215  bool negate = !empty && !sign_part.empty() && sign_part.front() == '-';
216  if (int_rep.empty()) int_rep.push_front('0');
217  if (negate) int_rep.push_front('-');
218  if (!frac_rep.empty()) frac_rep.push_front('.');
219 
220  // Construct the final value using the canonical representation.
221  std::string rep;
222  rep.reserve(int_rep.size() + frac_rep.size());
223  rep.append(int_rep.begin(), int_rep.end());
224  rep.append(frac_rep.begin(), frac_rep.end());
225  return spanner::Numeric(std::move(rep));
226 }
227 
228 // Like `MakeNumeric(s)`, but with an out-of-band exponent.
229 StatusOr<spanner::Numeric> MakeNumeric(std::string s, int exponent) {
230  if (exponent != 0) s += 'e' + std::to_string(exponent);
231  return MakeNumeric(std::move(s));
232 }
233 
234 } // namespace SPANNER_CLIENT_NS
235 } // namespace spanner_internal
236 
237 namespace spanner {
238 inline namespace SPANNER_CLIENT_NS {
239 
240 constexpr std::size_t Numeric::kIntPrec;
241 constexpr std::size_t Numeric::kFracPrec;
242 
243 Numeric::Numeric() : rep_("0") {}
244 
245 StatusOr<Numeric> MakeNumeric(std::string s) {
246  return spanner_internal::MakeNumeric(std::move(s));
247 }
248 
249 StatusOr<Numeric> MakeNumeric(double d) {
250  std::ostringstream ss;
251  ss.imbue(std::locale::classic());
252  ss << std::setprecision(std::numeric_limits<double>::digits10 + 1) << d;
253  std::string s = std::move(ss).str();
254  if (!std::isfinite(d)) return spanner_internal::OutOfRange(std::move(s));
255  return spanner_internal::MakeNumeric(std::move(s));
256 }
257 
258 } // namespace SPANNER_CLIENT_NS
259 } // namespace spanner
260 } // namespace cloud
261 } // namespace google