Skip to content

Commit 40fcf8f

Browse files
authored
Fix casting from string to decimal (#281)
1 parent 97d2829 commit 40fcf8f

File tree

1 file changed

+83
-6
lines changed

1 file changed

+83
-6
lines changed

velox/type/DecimalUtilOp.h

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,17 +166,94 @@ class DecimalUtilOp {
166166
}
167167
}
168168

169+
// Convert a number of scientific notation to normal.
170+
inline static std::string getNormalNumber(const std::string& value) {
171+
size_t dotPos = value.find('.');
172+
size_t expPos = value.find('E');
173+
if (expPos == std::string::npos) {
174+
return value;
175+
}
176+
177+
std::string ints;
178+
std::string digits;
179+
// Get the integers and digits from the base number.
180+
if (dotPos == std::string::npos) {
181+
ints = value.substr(0, expPos);
182+
digits = "";
183+
} else {
184+
ints = value.substr(0, dotPos);
185+
digits = value.substr(dotPos + 1, expPos - dotPos - 1);
186+
}
187+
188+
size_t pos = value.find("E+");
189+
// Handle number with positive exponent.
190+
if (pos != std::string::npos) {
191+
int exponent = std::stoi(value.substr(pos + 2, value.length()));
192+
std::string number = ints;
193+
if (exponent >= digits.length()) {
194+
// Dot is not needed.
195+
number = ints + digits;
196+
for (int i = 0; i < exponent - digits.length(); i++) {
197+
number += '0';
198+
}
199+
} else {
200+
number += digits.substr(0, exponent) + '.' +
201+
digits.substr(exponent + 1, digits.length());
202+
}
203+
return number;
204+
}
205+
pos = value.find("E-");
206+
if (pos != std::string::npos) {
207+
int exponent = std::stoi(value.substr(pos + 2, value.length()));
208+
std::string number;
209+
if (exponent < ints.length()) {
210+
number = ints.substr(0, ints.length() - exponent) + '.' +
211+
ints.substr(ints.length() - exponent + 1, ints.length());
212+
} else {
213+
number = "0.";
214+
for (int i = 0; i < exponent - ints.length(); i++) {
215+
number += '0';
216+
}
217+
number += ints;
218+
number += digits;
219+
}
220+
return number;
221+
}
222+
return value;
223+
}
224+
225+
// Round double to certain precision with half up.
226+
inline static double roundTo(double value, int precision) {
227+
int charsNeeded = 1 + snprintf(NULL, 0, "%.*f", (int)precision, value);
228+
char* buffer = reinterpret_cast<char*>(malloc(charsNeeded));
229+
double nextValue;
230+
if (value < 0) {
231+
nextValue = nextafter(value, value - 0.1);
232+
} else {
233+
nextValue = nextafter(value, value + 0.1);
234+
}
235+
snprintf(buffer, charsNeeded, "%.*f", (int)precision, nextValue);
236+
return atof(buffer);
237+
}
238+
169239
// return unscaled value and scale
170240
inline static std::pair<std::string, uint8_t> splitVarChar(
171-
const StringView& value) {
172-
std::string s = value.str();
241+
const StringView& value,
242+
int toScale) {
243+
std::string s = getNormalNumber(value.str());
173244
size_t pos = s.find('.');
174245
if (pos == std::string::npos) {
175246
return {s.substr(0, pos), 0};
247+
} else if (toScale < s.length() - pos - 1) {
248+
// If toScale is less than scales.length(), the string scales will be cut
249+
// and rounded.
250+
std::string roundedValue = std::to_string(roundTo(std::stod(s), toScale));
251+
pos = roundedValue.find('.');
252+
std::string scales = roundedValue.substr(pos + 1, toScale);
253+
return {roundedValue.substr(0, pos) + scales, scales.length()};
176254
} else {
177-
return {
178-
s.substr(0, pos) + s.substr(pos + 1, s.length()),
179-
s.length() - pos - 1};
255+
std::string scales = s.substr(pos + 1, s.length());
256+
return {s.substr(0, pos) + scales, scales.length()};
180257
}
181258
}
182259

@@ -237,7 +314,7 @@ class DecimalUtilOp {
237314
static_assert(
238315
std::is_same_v<TOutput, UnscaledShortDecimal> ||
239316
std::is_same_v<TOutput, UnscaledLongDecimal>);
240-
auto [unscaledStr, fromScale] = splitVarChar(inputValue);
317+
auto [unscaledStr, fromScale] = splitVarChar(inputValue, toScale);
241318
uint8_t fromPrecision = unscaledStr.size();
242319
VELOX_CHECK_LE(
243320
fromPrecision, DecimalType<TypeKind::LONG_DECIMAL>::kMaxPrecision);

0 commit comments

Comments
 (0)