blob: ad8fbc5b27bfe504f7b7edf663ec1860351a75a2 [file]
// 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.
// Operations that can deal with very large values (256-bit).
//
// The intermediate results with decimal can be larger than what can fit into 128-bit,
// but the final results can fit in 128-bit after scaling down. These functions deal
// with operations on the intermediate values.
//
#include "gandiva/decimal_xlarge.h"
#include <boost/multiprecision/cpp_int.hpp>
#include <limits>
#include <vector>
#include "arrow/util/basic_decimal.h"
#include "arrow/util/logging_internal.h"
#include "gandiva/decimal_type_util.h"
#ifndef GANDIVA_UNIT_TEST
# include "gandiva/engine.h"
# include "gandiva/exported_funcs.h"
namespace gandiva {
arrow::Status ExportedDecimalFunctions::AddMappings(Engine* engine) const {
std::vector<llvm::Type*> args;
auto types = engine->types();
// gdv_multiply_and_scale_down
args = {types->i64_type(), // int64_t x_high
types->i64_type(), // uint64_t x_low
types->i64_type(), // int64_t y_high
types->i64_type(), // uint64_t x_low
types->i32_type(), // int32_t reduce_scale_by
types->i64_ptr_type(), // int64_t* out_high
types->i64_ptr_type(), // uint64_t* out_low
types->i8_ptr_type()}; // bool* overflow
engine->AddGlobalMappingForFunc(
"gdv_xlarge_multiply_and_scale_down", types->void_type() /*return_type*/, args,
reinterpret_cast<void*>(gdv_xlarge_multiply_and_scale_down));
// gdv_xlarge_scale_up_and_divide
args = {types->i64_type(), // int64_t x_high
types->i64_type(), // uint64_t x_low
types->i64_type(), // int64_t y_high
types->i64_type(), // uint64_t y_low
types->i32_type(), // int32_t increase_scale_by
types->i64_ptr_type(), // int64_t* out_high
types->i64_ptr_type(), // uint64_t* out_low
types->i8_ptr_type()}; // bool* overflow
engine->AddGlobalMappingForFunc(
"gdv_xlarge_scale_up_and_divide", types->void_type() /*return_type*/, args,
reinterpret_cast<void*>(gdv_xlarge_scale_up_and_divide));
// gdv_xlarge_mod
args = {types->i64_type(), // int64_t x_high
types->i64_type(), // uint64_t x_low
types->i32_type(), // int32_t x_scale
types->i64_type(), // int64_t y_high
types->i64_type(), // uint64_t y_low
types->i32_type(), // int32_t y_scale
types->i64_ptr_type(), // int64_t* out_high
types->i64_ptr_type()}; // uint64_t* out_low
engine->AddGlobalMappingForFunc("gdv_xlarge_mod", types->void_type() /*return_type*/,
args, reinterpret_cast<void*>(gdv_xlarge_mod));
// gdv_xlarge_compare
args = {types->i64_type(), // int64_t x_high
types->i64_type(), // uint64_t x_low
types->i32_type(), // int32_t x_scale
types->i64_type(), // int64_t y_high
types->i64_type(), // uint64_t y_low
types->i32_type()}; // int32_t y_scale
engine->AddGlobalMappingForFunc("gdv_xlarge_compare", types->i32_type() /*return_type*/,
args, reinterpret_cast<void*>(gdv_xlarge_compare));
return arrow::Status::OK();
}
} // namespace gandiva
#endif // !GANDIVA_UNIT_TEST
using arrow::BasicDecimal128;
using boost::multiprecision::int256_t;
namespace gandiva {
namespace internal {
// Convert to 256-bit integer from 128-bit decimal.
static int256_t ConvertToInt256(BasicDecimal128 in) {
int256_t v = in.high_bits();
v <<= 64;
v |= in.low_bits();
return v;
}
// Convert to 128-bit decimal from 256-bit integer.
// If there is an overflow, the output is undefined.
static BasicDecimal128 ConvertToDecimal128(int256_t in, bool* overflow) {
BasicDecimal128 result;
constexpr int256_t UINT64_MASK = std::numeric_limits<uint64_t>::max();
int256_t in_abs = abs(in);
bool is_negative = in < 0;
uint64_t low = (in_abs & UINT64_MASK).convert_to<uint64_t>();
in_abs >>= 64;
uint64_t high = (in_abs & UINT64_MASK).convert_to<uint64_t>();
in_abs >>= 64;
if (in_abs > 0) {
// we've shifted in by 128-bit, so nothing should be left.
*overflow = true;
} else if (high > INT64_MAX) {
// the high-bit must not be set (signed 128-bit).
*overflow = true;
} else {
result = BasicDecimal128(static_cast<int64_t>(high), low);
if (result > BasicDecimal128::GetMaxValue()) {
*overflow = true;
}
}
return is_negative ? -result : result;
}
static constexpr int32_t kMaxLargeScale = 2 * DecimalTypeUtil::kMaxPrecision;
// Compute the scale multipliers once.
static std::array<int256_t, kMaxLargeScale + 1> kLargeScaleMultipliers =
([]() -> std::array<int256_t, kMaxLargeScale + 1> {
std::array<int256_t, kMaxLargeScale + 1> values;
values[0] = 1;
for (int32_t idx = 1; idx <= kMaxLargeScale; idx++) {
values[idx] = values[idx - 1] * 10;
}
return values;
})();
static int256_t GetScaleMultiplier(int scale) {
DCHECK_GE(scale, 0);
DCHECK_LE(scale, kMaxLargeScale);
return kLargeScaleMultipliers[scale];
}
// divide input by 10^reduce_by, and round up the fractional part.
static int256_t ReduceScaleBy(int256_t in, int32_t reduce_by) {
if (reduce_by == 0) {
// nothing to do.
return in;
}
int256_t divisor = GetScaleMultiplier(reduce_by);
DCHECK_GT(divisor, 0);
DCHECK_EQ(divisor % 2, 0); // multiple of 10.
auto result = in / divisor;
auto remainder = in % divisor;
// round up (same as BasicDecimal128::ReduceScaleBy)
if (abs(remainder) >= (divisor >> 1)) {
result += (in > 0 ? 1 : -1);
}
return result;
}
// multiply input by 10^increase_by.
static int256_t IncreaseScaleBy(int256_t in, int32_t increase_by) {
DCHECK_GE(increase_by, 0);
DCHECK_LE(increase_by, 2 * DecimalTypeUtil::kMaxPrecision);
return in * GetScaleMultiplier(increase_by);
}
} // namespace internal
} // namespace gandiva
extern "C" {
void gdv_xlarge_multiply_and_scale_down(int64_t x_high, uint64_t x_low, int64_t y_high,
uint64_t y_low, int32_t reduce_scale_by,
int64_t* out_high, uint64_t* out_low,
bool* overflow) {
BasicDecimal128 x{x_high, x_low};
BasicDecimal128 y{y_high, y_low};
auto intermediate_result =
gandiva::internal::ConvertToInt256(x) * gandiva::internal::ConvertToInt256(y);
intermediate_result =
gandiva::internal::ReduceScaleBy(intermediate_result, reduce_scale_by);
auto result = gandiva::internal::ConvertToDecimal128(intermediate_result, overflow);
*out_high = result.high_bits();
*out_low = result.low_bits();
}
void gdv_xlarge_scale_up_and_divide(int64_t x_high, uint64_t x_low, int64_t y_high,
uint64_t y_low, int32_t increase_scale_by,
int64_t* out_high, uint64_t* out_low,
bool* overflow) {
BasicDecimal128 x{x_high, x_low};
BasicDecimal128 y{y_high, y_low};
int256_t x_large = gandiva::internal::ConvertToInt256(x);
int256_t x_large_scaled_up =
gandiva::internal::IncreaseScaleBy(x_large, increase_scale_by);
int256_t y_large = gandiva::internal::ConvertToInt256(y);
int256_t result_large = x_large_scaled_up / y_large;
int256_t remainder_large = x_large_scaled_up % y_large;
// Since we are scaling up and then, scaling down, round-up the result (+1 for +ve,
// -1 for -ve), if the remainder is >= 2 * divisor.
if (abs(2 * remainder_large) >= abs(y_large)) {
// x +ve and y +ve, result is +ve => (1 ^ 1) + 1 = 0 + 1 = +1
// x +ve and y -ve, result is -ve => (-1 ^ 1) + 1 = -2 + 1 = -1
// x +ve and y -ve, result is -ve => (1 ^ -1) + 1 = -2 + 1 = -1
// x -ve and y -ve, result is +ve => (-1 ^ -1) + 1 = 0 + 1 = +1
result_large += (x.Sign() ^ y.Sign()) + 1;
}
auto result = gandiva::internal::ConvertToDecimal128(result_large, overflow);
*out_high = result.high_bits();
*out_low = result.low_bits();
}
void gdv_xlarge_mod(int64_t x_high, uint64_t x_low, int32_t x_scale, int64_t y_high,
uint64_t y_low, int32_t y_scale, int64_t* out_high,
uint64_t* out_low) {
BasicDecimal128 x{x_high, x_low};
BasicDecimal128 y{y_high, y_low};
int256_t x_large = gandiva::internal::ConvertToInt256(x);
int256_t y_large = gandiva::internal::ConvertToInt256(y);
if (x_scale < y_scale) {
x_large = gandiva::internal::IncreaseScaleBy(x_large, y_scale - x_scale);
} else {
y_large = gandiva::internal::IncreaseScaleBy(y_large, x_scale - y_scale);
}
auto intermediate_result = x_large % y_large;
bool overflow = false;
auto result = gandiva::internal::ConvertToDecimal128(intermediate_result, &overflow);
DCHECK_EQ(overflow, false);
*out_high = result.high_bits();
*out_low = result.low_bits();
}
int32_t gdv_xlarge_compare(int64_t x_high, uint64_t x_low, int32_t x_scale,
int64_t y_high, uint64_t y_low, int32_t y_scale) {
BasicDecimal128 x{x_high, x_low};
BasicDecimal128 y{y_high, y_low};
int256_t x_large = gandiva::internal::ConvertToInt256(x);
int256_t y_large = gandiva::internal::ConvertToInt256(y);
if (x_scale < y_scale) {
x_large = gandiva::internal::IncreaseScaleBy(x_large, y_scale - x_scale);
} else {
y_large = gandiva::internal::IncreaseScaleBy(y_large, x_scale - y_scale);
}
if (x_large == y_large) {
return 0;
} else if (x_large < y_large) {
return -1;
} else {
return 1;
}
}
} // extern "C"