Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion cpp/src/arrow/compute/kernels/scalar_cast_numeric.cc
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,36 @@ struct DecimalConversions<Decimal32, InDecimal> {
static Decimal32 ConvertOutput(Decimal32&& val) { return val; }
};

// Safe casts to Decimal32 need to rescale the full input coefficient before
// narrowing it. Otherwise, high bits can be lost before overflow and precision
// checks run.
template <typename OutDecimal, typename InDecimal>
struct SafeDecimalConversions : DecimalConversions<OutDecimal, InDecimal> {};

template <>
struct SafeDecimalConversions<Decimal32, Decimal64> {
static Decimal64 ConvertInput(Decimal64&& val) { return val; }
static Decimal32 ConvertOutput(Decimal64&& val) {
return Decimal32(static_cast<int32_t>(val.low_bits()));
}
};

template <>
struct SafeDecimalConversions<Decimal32, Decimal128> {
static Decimal128 ConvertInput(Decimal128&& val) { return val; }
static Decimal32 ConvertOutput(Decimal128&& val) {
return Decimal32(static_cast<int32_t>(val.low_bits()));
}
};

template <>
struct SafeDecimalConversions<Decimal32, Decimal256> {
static Decimal256 ConvertInput(Decimal256&& val) { return val; }
static Decimal32 ConvertOutput(Decimal256&& val) {
return Decimal32(static_cast<int32_t>(val.low_bits()));
}
};

template <>
struct DecimalConversions<Decimal64, Decimal32> {
// Convert then scale
Expand Down Expand Up @@ -573,7 +603,7 @@ struct UnsafeDownscaleDecimal {
struct SafeRescaleDecimal {
template <typename OutValue, typename Arg0Value>
OutValue Call(KernelContext*, Arg0Value val, Status* st) const {
using Conv = DecimalConversions<OutValue, Arg0Value>;
using Conv = SafeDecimalConversions<OutValue, Arg0Value>;
auto maybe_rescaled =
Conv::ConvertInput(std::move(val)).Rescale(in_scale_, out_scale_);
if (ARROW_PREDICT_FALSE(!maybe_rescaled.ok())) {
Expand Down
18 changes: 18 additions & 0 deletions cpp/src/arrow/compute/kernels/scalar_cast_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1646,6 +1646,24 @@ TEST(Cast, Decimal64ToDecimal256) {
TEST(Cast, Decimal128ToDecimal32) {
CastOptions options;

// Safe downcasts must check the value after rescaling the full input
// coefficient, before narrowing it to Decimal32.
auto d128_12_0_overflow = ArrayFromJSON(decimal128(12, 0), R"(["5000000000"])");
options.to_type = decimal32(9, 0);
CheckCastFails(d128_12_0_overflow, options);

auto d128_12_0_rescaled = ArrayFromJSON(decimal128(12, 0), R"(["536870912000"])");
Decimal32Builder d32_9_neg3_builder(decimal32(9, -3));
ASSERT_OK(d32_9_neg3_builder.Append(Decimal32(536870912)));
ASSERT_OK_AND_ASSIGN(auto d32_9_neg3, d32_9_neg3_builder.Finish());
options.to_type = d32_9_neg3->type();
CheckCast(d128_12_0_rescaled, d32_9_neg3, options);

auto d128_10_2_rescaled = ArrayFromJSON(decimal128(10, 2), R"(["30000000.00"])");
auto d32_9_0 = ArrayFromJSON(decimal32(9, 0), R"(["30000000"])");
options.to_type = d32_9_0->type();
CheckCast(d128_10_2_rescaled, d32_9_0, options);

for (bool allow_decimal_truncate : {false, true}) {
options.allow_decimal_truncate = allow_decimal_truncate;

Expand Down
Loading