blob: f88678738321f899e2730b68f115eddb0f9de90c [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#ifndef IMPALA_RUNTIME_DECIMAL_VALUE_INLINE_H
#define IMPALA_RUNTIME_DECIMAL_VALUE_INLINE_H
#include "runtime/decimal-value.h"
#include <iomanip>
#include <ostream>
#include <sstream>
#include "common/logging.h"
#include "util/decimal-util.h"
#include "util/hash-util.h"
namespace impala {
template<typename T>
inline DecimalValue<T> DecimalValue<T>::FromDouble(int precision, int scale, double d,
bool* overflow) {
// Check overflow.
T max_value = DecimalUtil::GetScaleMultiplier<T>(precision - scale);
if (abs(d) >= max_value) {
*overflow = true;
return DecimalValue();
}
// Multiply the double by the scale.
d *= DecimalUtil::GetScaleMultiplier<double>(scale);
// Truncate and just take the integer part.
return DecimalValue(static_cast<T>(d));
}
template<typename T>
inline DecimalValue<T> DecimalValue<T>::FromInt(int precision, int scale, int64_t d,
bool* overflow) {
// Check overflow. For scale 3, the max value is 10^3 - 1 = 999.
T max_value = DecimalUtil::GetScaleMultiplier<T>(precision - scale);
if (abs(d) >= max_value) {
*overflow = true;
return DecimalValue();
}
return DecimalValue(DecimalUtil::MultiplyByScale<T>(d, scale));
}
template<typename T>
inline int DecimalValue<T>::Compare(const DecimalValue& other) const {
T x = value();
T y = other.value();
if (x == y) return 0;
if (x < y) return -1;
return 1;
}
template<typename T>
inline const T DecimalValue<T>::whole_part(int scale) const {
return value() / DecimalUtil::GetScaleMultiplier<T>(scale);
}
template<typename T>
inline const T DecimalValue<T>::fractional_part(int scale) const {
return abs(value()) % DecimalUtil::GetScaleMultiplier<T>(scale);
}
template<typename T>
inline DecimalValue<T> DecimalValue<T>::ScaleTo(int src_scale, int dst_scale,
int dst_precision, bool* overflow) const { int delta_scale = src_scale - dst_scale;
T result = value();
T max_value = DecimalUtil::GetScaleMultiplier<T>(dst_precision);
if (delta_scale >= 0) {
if (delta_scale != 0) result /= DecimalUtil::GetScaleMultiplier<T>(delta_scale);
// Even if we are decreasing the absolute unscaled value, we can still overflow.
// This path is also used to convert between precisions so for example, converting
// from 100 as decimal(3,0) to decimal(2,0) should be considered an overflow.
*overflow |= abs(result) >= max_value;
} else if (delta_scale < 0) {
T mult = DecimalUtil::GetScaleMultiplier<T>(-delta_scale);
*overflow |= abs(result) >= max_value / mult;
result *= mult;
}
return DecimalValue(result);
}
// Use __builtin_add_overflow on GCC if available.
// Avoid using on Clang: it regresses performance.
#if 5 <= __GNUC__
template<typename T>
template<typename RESULT_T>
inline DecimalValue<RESULT_T> DecimalValue<T>::Add(int this_scale,
const DecimalValue& other, int other_scale, int result_precision, int result_scale,
bool* overflow) const {
DCHECK_EQ(result_scale, std::max(this_scale, other_scale));
RESULT_T x = 0;
RESULT_T y = 0;
*overflow |= AdjustToSameScale(*this, this_scale, other, other_scale,
result_precision, &x, &y);
if (result_precision == ColumnType::MAX_PRECISION) {
DCHECK_EQ(sizeof(RESULT_T), 16);
RESULT_T result = 0;
*overflow |= __builtin_add_overflow(x, y, &result);
*overflow |= abs(result) > DecimalUtil::MAX_UNSCALED_DECIMAL16;
return DecimalValue<RESULT_T>(result);
} else {
DCHECK(!*overflow) << "Cannot overflow unless result is Decimal16Value";
}
return DecimalValue<RESULT_T>(x + y);
}
#else
template<typename T>
template<typename RESULT_T>
inline DecimalValue<RESULT_T> DecimalValue<T>::Add(int this_scale,
const DecimalValue& other, int other_scale, int result_precision, int result_scale,
bool* overflow) const {
DCHECK_EQ(result_scale, std::max(this_scale, other_scale));
RESULT_T x = 0;
RESULT_T y = 0;
*overflow |= AdjustToSameScale(*this, this_scale, other, other_scale,
result_precision, &x, &y);
if (sizeof(RESULT_T) == 16) {
// Check overflow.
if (!*overflow && is_negative() == other.is_negative() &&
result_precision == ColumnType::MAX_PRECISION) {
// Can only overflow if the signs are the same and result precision reaches
// max precision.
*overflow |= DecimalUtil::MAX_UNSCALED_DECIMAL16 - abs(x) < abs(y);
// TODO: faster to return here? We don't care at all about the perf on
// the overflow case but what makes the normal path faster?
}
} else {
DCHECK(!*overflow) << "Cannot overflow unless result is Decimal16Value";
}
return DecimalValue<RESULT_T>(x + y);
}
#endif
// Use __builtin_mul_overflow on GCC if available.
// Avoid using on Clang: it requires a function __muloti present in the Clang runtime
// library but not the GCC runtime library and regresses performance.
#if 5 <= __GNUC__
template<typename T>
template<typename RESULT_T>
inline DecimalValue<RESULT_T> DecimalValue<T>::Multiply(int this_scale,
const DecimalValue& other, int other_scale, int result_precision, int result_scale,
bool* overflow) const {
// In the non-overflow case, we don't need to adjust by the scale since
// that is already handled by the FE when it computes the result decimal type.
// e.g. 1.23 * .2 (scale 2, scale 1 respectively) is identical to:
// 123 * 2 with a resulting scale 3. We can do the multiply on the unscaled values.
// The result scale in this case is the sum of the input scales.
RESULT_T x = value();
RESULT_T y = other.value();
if (x == 0 || y == 0) {
// Handle zero to avoid divide by zero in the overflow check below.
return DecimalValue<RESULT_T>(0);
}
RESULT_T result = 0;
if (result_precision == ColumnType::MAX_PRECISION) {
DCHECK_EQ(sizeof(RESULT_T), 16);
// Check overflow
*overflow |= __builtin_mul_overflow(x, y, &result);
*overflow |= abs(result) > DecimalUtil::MAX_UNSCALED_DECIMAL16;
} else {
result = x * y;
}
int delta_scale = this_scale + other_scale - result_scale;
if (UNLIKELY(delta_scale != 0)) {
// In this case, the required resulting scale is larger than the max we support.
// We cap the resulting scale to the max supported scale (e.g. truncate) in the FE.
// TODO: we could also return NULL.
DCHECK_GT(delta_scale, 0);
result /= DecimalUtil::GetScaleMultiplier<T>(delta_scale);
}
return DecimalValue<RESULT_T>(result);
}
#else
template<typename T>
template<typename RESULT_T>
DecimalValue<RESULT_T> DecimalValue<T>::Multiply(int this_scale,
const DecimalValue& other, int other_scale, int result_precision, int result_scale,
bool* overflow) const {
// In the non-overflow case, we don't need to adjust by the scale since
// that is already handled by the FE when it computes the result decimal type.
// e.g. 1.23 * .2 (scale 2, scale 1 respectively) is identical to:
// 123 * 2 with a resulting scale 3. We can do the multiply on the unscaled values.
// The result scale in this case is the sum of the input scales.
RESULT_T x = value();
RESULT_T y = other.value();
if (x == 0 || y == 0) {
// Handle zero to avoid divide by zero in the overflow check below.
return DecimalValue<RESULT_T>(0);
}
if (result_precision == ColumnType::MAX_PRECISION) {
DCHECK_EQ(sizeof(RESULT_T), 16);
// Check overflow
*overflow |= DecimalUtil::MAX_UNSCALED_DECIMAL16 / abs(y) < abs(x);
}
RESULT_T result = x * y;
int delta_scale = this_scale + other_scale - result_scale;
if (UNLIKELY(delta_scale != 0)) {
// In this case, the required resulting scale is larger than the max we support.
// We cap the resulting scale to the max supported scale (e.g. truncate) in the FE.
// TODO: we could also return NULL.
DCHECK_GT(delta_scale, 0);
result /= DecimalUtil::GetScaleMultiplier<T>(delta_scale);
}
return DecimalValue<RESULT_T>(result);
}
#endif
template<typename T>
template<typename RESULT_T>
inline DecimalValue<RESULT_T> DecimalValue<T>::Divide(int this_scale,
const DecimalValue& other, int other_scale, int result_precision, int result_scale,
bool* is_nan, bool* overflow) const {
DCHECK_GE(result_scale, this_scale);
if (other.value() == 0) {
// Divide by 0.
*is_nan = true;
return DecimalValue<RESULT_T>();
}
// We need to scale x up by the result precision and then do an integer divide.
// This truncates the result to the output precision.
// TODO: confirm with standard that truncate is okay.
int scale_by = result_scale + other_scale - this_scale;
// Use higher precision ints for intermediates to avoid overflows. Divides lead to
// large numbers very quickly (and get eliminated by the int divide).
if (sizeof(T) == 16) {
int256_t x = DecimalUtil::MultiplyByScale<int256_t>(
ConvertToInt256(value()), scale_by);
int256_t y = ConvertToInt256(other.value());
int128_t r = ConvertToInt128(x / y, DecimalUtil::MAX_UNSCALED_DECIMAL16, overflow);
return DecimalValue<RESULT_T>(r);
} else {
int128_t x = DecimalUtil::MultiplyByScale<RESULT_T>(value(), scale_by);
int128_t y = other.value();
int128_t r = x / y;
return DecimalValue<RESULT_T>(static_cast<RESULT_T>(r));
}
}
template<typename T>
template<typename RESULT_T>
inline DecimalValue<RESULT_T> DecimalValue<T>::Mod(int this_scale,
const DecimalValue& other, int other_scale, int result_precision, int result_scale,
bool* is_nan, bool* overflow) const {
DCHECK_EQ(result_scale, std::max(this_scale, other_scale));
if (other.value() == 0) {
// Mod by 0.
*is_nan = true;
return DecimalValue<RESULT_T>();
}
*is_nan = false;
RESULT_T x = 0;
RESULT_T y = 1; // Initialize y to avoid mod by 0.
*overflow |= AdjustToSameScale(*this, this_scale, other, other_scale,
result_precision, &x, &y);
return DecimalValue<RESULT_T>(x % y);
}
template<typename T>
template <typename RESULT_T>
inline bool DecimalValue<T>::AdjustToSameScale(const DecimalValue<T>& x, int x_scale,
const DecimalValue<T>& y, int y_scale, int result_precision, RESULT_T* x_scaled,
RESULT_T* y_scaled) {
int delta_scale = x_scale - y_scale;
RESULT_T scale_factor = DecimalUtil::GetScaleMultiplier<RESULT_T>(abs(delta_scale));
if (delta_scale == 0) {
*x_scaled = x.value();
*y_scaled = y.value();
} else if (delta_scale > 0) {
if (sizeof(RESULT_T) == 16 && result_precision == ColumnType::MAX_PRECISION &&
DecimalUtil::GetScaleQuotient(delta_scale) < abs(y.value())) {
return true;
}
*x_scaled = x.value();
*y_scaled = y.value() * scale_factor;
} else {
if (sizeof(RESULT_T) == 16 && result_precision == ColumnType::MAX_PRECISION &&
DecimalUtil::GetScaleQuotient(-delta_scale) < abs(x.value())) {
return true;
}
*x_scaled = x.value() * scale_factor;
*y_scaled = y.value();
}
return false;
}
/// For comparisons, we need the intermediate to be at the next precision
/// to avoid overflows.
/// TODO: is there a more efficient way to do this?
template <>
inline int Decimal4Value::Compare(int this_scale, const Decimal4Value& other,
int other_scale) const {
int64_t x, y;
bool overflow = AdjustToSameScale(*this, this_scale, other, other_scale, 0, &x, &y);
DCHECK(!overflow) << "Overflow cannot happen with Decimal4Value";
if (x == y) return 0;
if (x < y) return -1;
return 1;
}
template <>
inline int Decimal8Value::Compare(int this_scale, const Decimal8Value& other,
int other_scale) const {
int128_t x = 0, y = 0;
bool overflow = AdjustToSameScale(*this, this_scale, other, other_scale, 0, &x, &y);
DCHECK(!overflow) << "Overflow cannot happen with Decimal8Value";
if (x == y) return 0;
if (x < y) return -1;
return 1;
}
template <>
inline int Decimal16Value::Compare(int this_scale, const Decimal16Value& other,
int other_scale) const {
int256_t x = ConvertToInt256(this->value());
int256_t y = ConvertToInt256(other.value());
int delta_scale = this_scale - other_scale;
if (delta_scale > 0) {
y = DecimalUtil::MultiplyByScale<int256_t>(y, delta_scale);
} else if (delta_scale < 0) {
x = DecimalUtil::MultiplyByScale<int256_t>(x, -delta_scale);
}
if (x == y) return 0;
if (x < y) return -1;
return 1;
}
/// Returns as string with full 0 padding on the right and single 0 padded on the left
/// if the whole part is zero otherwise there will be no left padding.
template<typename T>
inline std::string DecimalValue<T>::ToString(const ColumnType& type) const {
DCHECK_EQ(type.type, TYPE_DECIMAL);
return ToString(type.precision, type.scale);
}
template<typename T>
inline std::string DecimalValue<T>::ToString(int precision, int scale) const {
// Decimal values are sent to clients as strings so in the interest of
// speed the string will be created without the using stringstream with the
// whole/fractional_part().
int last_char_idx = precision
+ (scale > 0) // Add a space for decimal place
+ (scale == precision) // Add a space for leading 0
+ (value_ < 0); // Add a space for negative sign
std::string str = std::string(last_char_idx, '0');
// Start filling in the values in reverse order by taking the last digit
// of the value. Use a positive value and worry about the sign later. At this
// point the last_char_idx points to the string terminator.
T remaining_value = value_;
int first_digit_idx = 0;
if (value_ < 0) {
remaining_value = -value_;
first_digit_idx = 1;
}
if (scale > 0) {
int remaining_scale = scale;
do {
str[--last_char_idx] = (remaining_value % 10) + '0'; // Ascii offset
remaining_value /= 10;
} while (--remaining_scale > 0);
str[--last_char_idx] = '.';
DCHECK_GT(last_char_idx, first_digit_idx) << "Not enough space remaining";
}
do {
str[--last_char_idx] = (remaining_value % 10) + '0'; // Ascii offset
remaining_value /= 10;
if (remaining_value == 0) {
// Trim any extra leading 0's.
if (last_char_idx > first_digit_idx) str.erase(0, last_char_idx - first_digit_idx);
break;
}
// For safety, enforce string length independent of remaining_value.
} while (last_char_idx > first_digit_idx);
if (value_ < 0) str[0] = '-';
return str;
}
template<typename T>
inline double DecimalValue<T>::ToDouble(int scale) const {
return static_cast<double>(value_) / powf(10.0, scale);
}
template<typename T>
inline uint32_t DecimalValue<T>::Hash(int seed) const {
return HashUtil::Hash(&value_, sizeof(value_), seed);
}
/// This function must be called 'hash_value' to be picked up by boost.
inline std::size_t hash_value(const Decimal4Value& v) {
return v.Hash();
}
inline std::size_t hash_value(const Decimal8Value& v) {
return v.Hash();
}
inline std::size_t hash_value(const Decimal16Value& v) {
return v.Hash();
}
template<typename T>
inline DecimalValue<T> DecimalValue<T>::Abs() const {
return DecimalValue<T>(abs(value_));
}
/// Conversions from different decimal types to one another. This does not
/// alter the scale. Checks for overflow. Although in some cases (going from Decimal4Value
/// to Decimal8Value) cannot overflow, the signature is the same to allow for templating.
inline Decimal4Value ToDecimal4(const Decimal4Value& v, bool* overflow) {
return v;
}
inline Decimal8Value ToDecimal8(const Decimal4Value& v, bool* overflow) {
return Decimal8Value(static_cast<int64_t>(v.value()));
}
inline Decimal16Value ToDecimal16(const Decimal4Value& v, bool* overflow) {
return Decimal16Value(static_cast<int128_t>(v.value()));
}
inline Decimal4Value ToDecimal4(const Decimal8Value& v, bool* overflow) {
*overflow |= abs(v.value()) > std::numeric_limits<int32_t>::max();
return Decimal4Value(static_cast<int32_t>(v.value()));
}
inline Decimal8Value ToDecimal8(const Decimal8Value& v, bool* overflow) {
return v;
}
inline Decimal16Value ToDecimal16(const Decimal8Value& v, bool* overflow) {
return Decimal16Value(static_cast<int128_t>(v.value()));
}
inline Decimal4Value ToDecimal4(const Decimal16Value& v, bool* overflow) {
*overflow |= abs(v.value()) > std::numeric_limits<int32_t>::max();
return Decimal4Value(static_cast<int32_t>(v.value()));
}
inline Decimal8Value ToDecimal8(const Decimal16Value& v, bool* overflow) {
*overflow |= abs(v.value()) > std::numeric_limits<int64_t>::max();
return Decimal8Value(static_cast<int64_t>(v.value()));
}
inline Decimal16Value ToDecimal16(const Decimal16Value& v, bool* overflow) {
return v;
}
}
#endif