This is an automated email from the ASF dual-hosted git repository.
zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new 313d11aa94 GH-43956: [C++][Compute] Add Decimal32/64 Casts (#45014)
313d11aa94 is described below
commit 313d11aa94c2be71142b55e3d8bb166d780c19c7
Author: Matt Topol <[email protected]>
AuthorDate: Fri Dec 13 11:19:51 2024 -0500
GH-43956: [C++][Compute] Add Decimal32/64 Casts (#45014)
<!--
Thanks for opening a pull request!
If this is your first pull request you can find detailed information on
how
to contribute here:
* [New Contributor's
Guide](https://arrow.apache.org/docs/dev/developers/guide/step_by_step/pr_lifecycle.html#reviews-and-merge-of-the-pull-request)
* [Contributing
Overview](https://arrow.apache.org/docs/dev/developers/overview.html)
If this is not a [minor
PR](https://github.com/apache/arrow/blob/main/CONTRIBUTING.md#Minor-Fixes).
Could you open an issue for this pull request on GitHub?
https://github.com/apache/arrow/issues/new/choose
Opening GitHub issues ahead of time contributes to the
[Openness](http://theapacheway.com/open/#:~:text=Openness%20allows%20new%20users%20the,must%20happen%20in%20the%20open.)
of the Apache Arrow project.
Then could you also rename the pull request title in the following
format?
GH-${GITHUB_ISSUE_ID}: [${COMPONENT}] ${SUMMARY}
or
MINOR: [${COMPONENT}] ${SUMMARY}
-->
### Rationale for this change
Furthering the support for Decimal32/Decimal64 among Acero and casting
functionality. This is also necessary for #44882 to add support for
Decimal32/64 to PyArrow
<!--
Why are you proposing this change? If this is already explained clearly
in the issue then this section is not needed.
Explaining clearly why changes are proposed helps reviewers understand
your changes and offer better suggestions for fixes.
-->
### What changes are included in this PR?
Adding kernels for casting to and from Decimal32/Decimal64 between
numeric, floating point, string and other decimal types.
<!--
There is no need to duplicate the description in the issue here but it
is sometimes worth providing a summary of the individual changes in this
PR.
-->
### Are these changes tested?
Yes, unit tests are added accordingly.
<!--
We typically require tests for all PRs in order to:
1. Prevent the code from being accidentally broken by subsequent changes
2. Serve as another way to document the expected behavior of the code
If tests are not included in your PR, please explain why (for example,
are they covered by existing tests)?
-->
* GitHub Issue: #43956
---
.../arrow/compute/kernels/scalar_cast_numeric.cc | 177 ++-
.../arrow/compute/kernels/scalar_cast_string.cc | 3 +-
cpp/src/arrow/compute/kernels/scalar_cast_test.cc | 1232 +++++++++++++++++++-
cpp/src/arrow/util/basic_decimal.h | 10 +
4 files changed, 1391 insertions(+), 31 deletions(-)
diff --git a/cpp/src/arrow/compute/kernels/scalar_cast_numeric.cc
b/cpp/src/arrow/compute/kernels/scalar_cast_numeric.cc
index 1fe26b3163..b000efd1e0 100644
--- a/cpp/src/arrow/compute/kernels/scalar_cast_numeric.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_cast_numeric.cc
@@ -479,6 +479,43 @@ struct DecimalConversions<Decimal256, InDecimal> {
static Decimal256 ConvertOutput(Decimal256&& val) { return val; }
};
+template <typename InDecimal>
+struct DecimalConversions<Decimal32, InDecimal> {
+ static Decimal32 ConvertInput(InDecimal&& val) { return
Decimal32(val.low_bits()); }
+ static Decimal32 ConvertOutput(Decimal32&& val) { return val; }
+};
+
+template <>
+struct DecimalConversions<Decimal64, Decimal32> {
+ // Convert then scale
+ static Decimal64 ConvertInput(Decimal32&& val) { return Decimal64(val); }
+ static Decimal64 ConvertOutput(Decimal64&& val) { return val; }
+};
+
+template <>
+struct DecimalConversions<Decimal64, Decimal64> {
+ static Decimal64 ConvertInput(Decimal64&& val) { return val; }
+ static Decimal64 ConvertOutput(Decimal64&& val) { return val; }
+};
+
+template <>
+struct DecimalConversions<Decimal64, Decimal128> {
+ // Scale then truncate
+ static Decimal128 ConvertInput(Decimal128&& val) { return val; }
+ static Decimal64 ConvertOutput(Decimal128&& val) {
+ return Decimal64(static_cast<int64_t>(val.low_bits()));
+ }
+};
+
+template <>
+struct DecimalConversions<Decimal64, Decimal256> {
+ // Scale then truncate
+ static Decimal256 ConvertInput(Decimal256&& val) { return val; }
+ static Decimal64 ConvertOutput(Decimal256&& val) {
+ return Decimal64(static_cast<int64_t>(val.low_bits()));
+ }
+};
+
template <>
struct DecimalConversions<Decimal128, Decimal256> {
// Scale then truncate
@@ -495,6 +532,20 @@ struct DecimalConversions<Decimal128, Decimal128> {
static Decimal128 ConvertOutput(Decimal128&& val) { return val; }
};
+template <>
+struct DecimalConversions<Decimal128, Decimal64> {
+ // convert then scale
+ static Decimal128 ConvertInput(Decimal64&& val) { return
Decimal128(val.value()); }
+ static Decimal128 ConvertOutput(Decimal128&& val) { return val; }
+};
+
+template <>
+struct DecimalConversions<Decimal128, Decimal32> {
+ // convert then scale
+ static Decimal128 ConvertInput(Decimal32&& val) { return
Decimal128(val.value()); }
+ static Decimal128 ConvertOutput(Decimal128&& val) { return val; }
+};
+
struct UnsafeUpscaleDecimal {
template <typename OutValue, typename Arg0Value>
OutValue Call(KernelContext*, Arg0Value val, Status*) const {
@@ -659,6 +710,18 @@ struct DecimalCastFunctor {
}
};
+template <typename I>
+struct CastFunctor<
+ Decimal32Type, I,
+ enable_if_t<is_base_binary_type<I>::value ||
is_binary_view_like_type<I>::value>>
+ : public DecimalCastFunctor<Decimal32Type, I> {};
+
+template <typename I>
+struct CastFunctor<
+ Decimal64Type, I,
+ enable_if_t<is_base_binary_type<I>::value ||
is_binary_view_like_type<I>::value>>
+ : public DecimalCastFunctor<Decimal64Type, I> {};
+
template <typename I>
struct CastFunctor<
Decimal128Type, I,
@@ -744,6 +807,10 @@ std::shared_ptr<CastFunction> GetCastToInteger(std::string
name) {
// From decimal to integer
DCHECK_OK(func->AddKernel(Type::DECIMAL, {InputType(Type::DECIMAL)}, out_ty,
CastFunctor<OutType, Decimal128Type>::Exec));
+ DCHECK_OK(func->AddKernel(Type::DECIMAL32, {InputType(Type::DECIMAL32)},
out_ty,
+ CastFunctor<OutType, Decimal32Type>::Exec));
+ DCHECK_OK(func->AddKernel(Type::DECIMAL64, {InputType(Type::DECIMAL64)},
out_ty,
+ CastFunctor<OutType, Decimal64Type>::Exec));
DCHECK_OK(func->AddKernel(Type::DECIMAL256, {InputType(Type::DECIMAL256)},
out_ty,
CastFunctor<OutType, Decimal256Type>::Exec));
return func;
@@ -772,6 +839,10 @@ std::shared_ptr<CastFunction>
GetCastToFloating(std::string name) {
AddCommonNumberCasts<OutType>(out_ty, func.get());
// From decimal to floating point
+ DCHECK_OK(func->AddKernel(Type::DECIMAL32, {InputType(Type::DECIMAL32)},
out_ty,
+ CastFunctor<OutType, Decimal32Type>::Exec));
+ DCHECK_OK(func->AddKernel(Type::DECIMAL64, {InputType(Type::DECIMAL64)},
out_ty,
+ CastFunctor<OutType, Decimal64Type>::Exec));
DCHECK_OK(func->AddKernel(Type::DECIMAL, {InputType(Type::DECIMAL)}, out_ty,
CastFunctor<OutType, Decimal128Type>::Exec));
DCHECK_OK(func->AddKernel(Type::DECIMAL256, {InputType(Type::DECIMAL256)},
out_ty,
@@ -780,6 +851,94 @@ std::shared_ptr<CastFunction>
GetCastToFloating(std::string name) {
return func;
}
+std::shared_ptr<CastFunction> GetCastToDecimal32() {
+ OutputType sig_out_ty(ResolveOutputFromOptions);
+
+ auto func = std::make_shared<CastFunction>("cast_decimal32",
Type::DECIMAL32);
+ AddCommonCasts(Type::DECIMAL32, sig_out_ty, func.get());
+
+ // Cast from floating point
+ DCHECK_OK(func->AddKernel(Type::FLOAT, {float32()}, sig_out_ty,
+ CastFunctor<Decimal32Type, FloatType>::Exec));
+ DCHECK_OK(func->AddKernel(Type::DOUBLE, {float64()}, sig_out_ty,
+ CastFunctor<Decimal32Type, DoubleType>::Exec));
+
+ // Cast from integer
+ for (const std::shared_ptr<DataType>& in_ty : IntTypes()) {
+ auto exec = GenerateInteger<CastFunctor, Decimal32Type>(in_ty->id());
+ DCHECK_OK(func->AddKernel(in_ty->id(), {in_ty}, sig_out_ty,
std::move(exec)));
+ }
+
+ // Cast from other strings
+ for (const std::shared_ptr<DataType>& in_ty : BaseBinaryTypes()) {
+ auto exec = GenerateVarBinaryBase<CastFunctor, Decimal32Type>(in_ty->id());
+ DCHECK_OK(func->AddKernel(in_ty->id(), {in_ty}, sig_out_ty,
std::move(exec)));
+ }
+ for (const std::shared_ptr<DataType>& in_ty : BinaryViewTypes()) {
+ auto exec = GenerateVarBinaryViewBase<CastFunctor,
Decimal32Type>(in_ty->id());
+ DCHECK_OK(func->AddKernel(in_ty->id(), {in_ty}, sig_out_ty,
std::move(exec)));
+ }
+
+ // Cast from other decimal
+ auto exec = CastFunctor<Decimal32Type, Decimal32Type>::Exec;
+ DCHECK_OK(
+ func->AddKernel(Type::DECIMAL32, {InputType(Type::DECIMAL32)},
sig_out_ty, exec));
+ exec = CastFunctor<Decimal32Type, Decimal64Type>::Exec;
+ DCHECK_OK(
+ func->AddKernel(Type::DECIMAL64, {InputType(Type::DECIMAL64)},
sig_out_ty, exec));
+ exec = CastFunctor<Decimal32Type, Decimal128Type>::Exec;
+ DCHECK_OK(
+ func->AddKernel(Type::DECIMAL128, {InputType(Type::DECIMAL128)},
sig_out_ty, exec));
+ exec = CastFunctor<Decimal32Type, Decimal256Type>::Exec;
+ DCHECK_OK(
+ func->AddKernel(Type::DECIMAL256, {InputType(Type::DECIMAL256)},
sig_out_ty, exec));
+ return func;
+}
+
+std::shared_ptr<CastFunction> GetCastToDecimal64() {
+ OutputType sig_out_ty(ResolveOutputFromOptions);
+
+ auto func = std::make_shared<CastFunction>("cast_decimal64",
Type::DECIMAL64);
+ AddCommonCasts(Type::DECIMAL64, sig_out_ty, func.get());
+
+ // Cast from floating point
+ DCHECK_OK(func->AddKernel(Type::FLOAT, {float32()}, sig_out_ty,
+ CastFunctor<Decimal64Type, FloatType>::Exec));
+ DCHECK_OK(func->AddKernel(Type::DOUBLE, {float64()}, sig_out_ty,
+ CastFunctor<Decimal64Type, DoubleType>::Exec));
+
+ // Cast from integer
+ for (const std::shared_ptr<DataType>& in_ty : IntTypes()) {
+ auto exec = GenerateInteger<CastFunctor, Decimal64Type>(in_ty->id());
+ DCHECK_OK(func->AddKernel(in_ty->id(), {in_ty}, sig_out_ty,
std::move(exec)));
+ }
+
+ // Cast from other strings
+ for (const std::shared_ptr<DataType>& in_ty : BaseBinaryTypes()) {
+ auto exec = GenerateVarBinaryBase<CastFunctor, Decimal64Type>(in_ty->id());
+ DCHECK_OK(func->AddKernel(in_ty->id(), {in_ty}, sig_out_ty,
std::move(exec)));
+ }
+ for (const std::shared_ptr<DataType>& in_ty : BinaryViewTypes()) {
+ auto exec = GenerateVarBinaryViewBase<CastFunctor,
Decimal64Type>(in_ty->id());
+ DCHECK_OK(func->AddKernel(in_ty->id(), {in_ty}, sig_out_ty,
std::move(exec)));
+ }
+
+ // Cast from other decimal
+ auto exec = CastFunctor<Decimal64Type, Decimal32Type>::Exec;
+ DCHECK_OK(
+ func->AddKernel(Type::DECIMAL32, {InputType(Type::DECIMAL32)},
sig_out_ty, exec));
+ exec = CastFunctor<Decimal64Type, Decimal64Type>::Exec;
+ DCHECK_OK(
+ func->AddKernel(Type::DECIMAL64, {InputType(Type::DECIMAL64)},
sig_out_ty, exec));
+ exec = CastFunctor<Decimal64Type, Decimal128Type>::Exec;
+ DCHECK_OK(
+ func->AddKernel(Type::DECIMAL128, {InputType(Type::DECIMAL128)},
sig_out_ty, exec));
+ exec = CastFunctor<Decimal64Type, Decimal256Type>::Exec;
+ DCHECK_OK(
+ func->AddKernel(Type::DECIMAL256, {InputType(Type::DECIMAL256)},
sig_out_ty, exec));
+ return func;
+}
+
std::shared_ptr<CastFunction> GetCastToDecimal128() {
OutputType sig_out_ty(ResolveOutputFromOptions);
@@ -809,8 +968,14 @@ std::shared_ptr<CastFunction> GetCastToDecimal128() {
}
// Cast from other decimal
- auto exec = CastFunctor<Decimal128Type, Decimal128Type>::Exec;
+ auto exec = CastFunctor<Decimal128Type, Decimal32Type>::Exec;
// We resolve the output type of this kernel from the CastOptions
+ DCHECK_OK(
+ func->AddKernel(Type::DECIMAL32, {InputType(Type::DECIMAL32)},
sig_out_ty, exec));
+ exec = CastFunctor<Decimal128Type, Decimal64Type>::Exec;
+ DCHECK_OK(
+ func->AddKernel(Type::DECIMAL64, {InputType(Type::DECIMAL64)},
sig_out_ty, exec));
+ exec = CastFunctor<Decimal128Type, Decimal128Type>::Exec;
DCHECK_OK(
func->AddKernel(Type::DECIMAL128, {InputType(Type::DECIMAL128)},
sig_out_ty, exec));
exec = CastFunctor<Decimal128Type, Decimal256Type>::Exec;
@@ -848,7 +1013,13 @@ std::shared_ptr<CastFunction> GetCastToDecimal256() {
}
// Cast from other decimal
- auto exec = CastFunctor<Decimal256Type, Decimal128Type>::Exec;
+ auto exec = CastFunctor<Decimal256Type, Decimal32Type>::Exec;
+ DCHECK_OK(
+ func->AddKernel(Type::DECIMAL32, {InputType(Type::DECIMAL32)},
sig_out_ty, exec));
+ exec = CastFunctor<Decimal256Type, Decimal64Type>::Exec;
+ DCHECK_OK(
+ func->AddKernel(Type::DECIMAL64, {InputType(Type::DECIMAL64)},
sig_out_ty, exec));
+ exec = CastFunctor<Decimal256Type, Decimal128Type>::Exec;
DCHECK_OK(
func->AddKernel(Type::DECIMAL128, {InputType(Type::DECIMAL128)},
sig_out_ty, exec));
exec = CastFunctor<Decimal256Type, Decimal256Type>::Exec;
@@ -950,6 +1121,8 @@ std::vector<std::shared_ptr<CastFunction>>
GetNumericCasts() {
auto cast_double = GetCastToFloating<DoubleType>("cast_double");
functions.push_back(cast_double);
+ functions.push_back(GetCastToDecimal32());
+ functions.push_back(GetCastToDecimal64());
functions.push_back(GetCastToDecimal128());
functions.push_back(GetCastToDecimal256());
diff --git a/cpp/src/arrow/compute/kernels/scalar_cast_string.cc
b/cpp/src/arrow/compute/kernels/scalar_cast_string.cc
index 4edf00225d..7186612d25 100644
--- a/cpp/src/arrow/compute/kernels/scalar_cast_string.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_cast_string.cc
@@ -683,7 +683,8 @@ void AddNumberToStringCasts(CastFunction* func) {
template <typename OutType>
void AddDecimalToStringCasts(CastFunction* func) {
auto out_ty = TypeTraits<OutType>::type_singleton();
- for (const auto& in_tid : std::vector<Type::type>{Type::DECIMAL128,
Type::DECIMAL256}) {
+ for (const auto& in_tid : std::vector<Type::type>{Type::DECIMAL32,
Type::DECIMAL64,
+ Type::DECIMAL128,
Type::DECIMAL256}) {
DCHECK_OK(
func->AddKernel(in_tid, {in_tid}, out_ty,
GenerateDecimal<DecimalToStringCastFunctor,
OutType>(in_tid),
diff --git a/cpp/src/arrow/compute/kernels/scalar_cast_test.cc
b/cpp/src/arrow/compute/kernels/scalar_cast_test.cc
index 33a0142550..80d5b3c46c 100644
--- a/cpp/src/arrow/compute/kernels/scalar_cast_test.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_cast_test.cc
@@ -447,6 +447,159 @@ TEST(Cast, IntToFloating) {
CastOptions::Safe(float64()));
}
+TEST(Cast, Decimal32ToInt) {
+ auto options = CastOptions::Safe(int32());
+
+ for (bool allow_int_overflow : {false, true}) {
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_int_overflow = allow_int_overflow;
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto no_overflow_no_truncation = ArrayFromJSON(decimal32(9, 5), R"([
+ "02.00000",
+ "-11.00000",
+ "22.00000",
+ "-121.00000",
+ null])");
+ CheckCast(no_overflow_no_truncation,
+ ArrayFromJSON(int32(), "[2, -11, 22, -121, null]"), options);
+ }
+ }
+
+ for (bool allow_int_overflow : {false, true}) {
+ options.allow_int_overflow = allow_int_overflow;
+ auto truncation_but_no_overflow = ArrayFromJSON(decimal32(9, 5), R"([
+ "02.10000",
+ "-11.00450",
+ "22.00045",
+ "-121.12100",
+ null])");
+
+ options.allow_decimal_truncate = true;
+ CheckCast(truncation_but_no_overflow,
+ ArrayFromJSON(int32(), "[2, -11, 22, -121, null]"), options);
+
+ options.allow_decimal_truncate = false;
+ CheckCastFails(truncation_but_no_overflow, options);
+ }
+
+ for (bool allow_int_overflow : {false, true}) {
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_int_overflow = allow_int_overflow;
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto overflow_and_truncation = ArrayFromJSON(decimal32(9, 5), R"([
+ "1234.00453",
+ "9999.00344",
+ null])");
+
+ if (options.allow_decimal_truncate) {
+ CheckCast(overflow_and_truncation, ArrayFromJSON(int32(), "[1234,
9999, null]"),
+ options);
+ } else {
+ CheckCastFails(overflow_and_truncation, options);
+ }
+ }
+ }
+
+ Decimal32Builder builder(decimal32(9, -3));
+ for (auto d : {Decimal32("12345000."), Decimal32("-12000000.")}) {
+ ASSERT_OK_AND_ASSIGN(d, d.Rescale(0, -3));
+ ASSERT_OK(builder.Append(d));
+ }
+ ASSERT_OK_AND_ASSIGN(auto negative_scale, builder.Finish());
+ options.allow_int_overflow = true;
+ options.allow_decimal_truncate = true;
+ CheckCast(negative_scale, ArrayFromJSON(int32(), "[12345000, -12000000]"),
options);
+}
+
+TEST(Cast, Decimal64ToInt) {
+ auto options = CastOptions::Safe(int64());
+
+ for (bool allow_int_overflow : {false, true}) {
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_int_overflow = allow_int_overflow;
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto no_overflow_no_truncation = ArrayFromJSON(decimal64(18, 10), R"([
+ "02.0000000000",
+ "-11.0000000000",
+ "22.0000000000",
+ "-121.0000000000",
+ null])");
+ CheckCast(no_overflow_no_truncation,
+ ArrayFromJSON(int64(), "[2, -11, 22, -121, null]"), options);
+ }
+ }
+
+ for (bool allow_int_overflow : {false, true}) {
+ options.allow_int_overflow = allow_int_overflow;
+ auto truncation_but_no_overflow = ArrayFromJSON(decimal64(18, 10), R"([
+ "02.1000000000",
+ "-11.0000004500",
+ "22.0000004500",
+ "-121.1210000000",
+ null])");
+
+ options.allow_decimal_truncate = true;
+ CheckCast(truncation_but_no_overflow,
+ ArrayFromJSON(int32(), "[2, -11, 22, -121, null]"), options);
+
+ options.allow_decimal_truncate = false;
+ CheckCastFails(truncation_but_no_overflow, options);
+ }
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto overflow_no_truncation = ArrayFromJSON(decimal64(18, 5), R"([
+ "1234567890123.00000",
+ "9999999999999.00000",
+ null])");
+
+ options.allow_int_overflow = true;
+ CheckCast(overflow_no_truncation,
+ ArrayFromJSON(int64(), "[1234567890123, 9999999999999, null]"),
options);
+
+ options.to_type = int32();
+ options.allow_int_overflow = false;
+ CheckCastFails(overflow_no_truncation, options);
+ }
+
+ for (bool allow_int_overflow : {false, true}) {
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_int_overflow = allow_int_overflow;
+ options.allow_decimal_truncate = allow_decimal_truncate;
+ options.to_type = int32();
+
+ auto overflow_and_truncation = ArrayFromJSON(decimal64(18, 5), R"([
+ "1234567890123.45345",
+ "9999999999999.00344",
+ null])");
+
+ if (options.allow_int_overflow && options.allow_decimal_truncate) {
+ CheckCast(overflow_and_truncation,
+ ArrayFromJSON(int32(),
+ // 1234567890123 % 2**32, 9999999999999 % 2**32
+ "[1912276171, 1316134911, null]"),
+ options);
+ } else {
+ CheckCastFails(overflow_and_truncation, options);
+ }
+ }
+ }
+
+ Decimal64Builder builder(decimal64(18, -4));
+ for (auto d : {Decimal64("1234567890000."), Decimal64("-120000.")}) {
+ ASSERT_OK_AND_ASSIGN(d, d.Rescale(0, -4));
+ ASSERT_OK(builder.Append(d));
+ }
+ ASSERT_OK_AND_ASSIGN(auto negative_scale, builder.Finish());
+ options.allow_int_overflow = true;
+ options.allow_decimal_truncate = true;
+ CheckCast(negative_scale, ArrayFromJSON(int64(), "[1234567890000,
-120000]"), options);
+}
+
TEST(Cast, Decimal128ToInt) {
auto options = CastOptions::Safe(int64());
@@ -629,11 +782,14 @@ TEST(Cast, Decimal256ToInt) {
}
TEST(Cast, IntegerToDecimal) {
- for (auto decimal_type : {decimal128(22, 2), decimal256(22, 2)}) {
+ for (auto decimal_type :
+ {decimal32(9, 2), decimal64(18, 2), decimal128(22, 2), decimal256(22,
2)}) {
for (auto integer_type : kIntegerTypes) {
- CheckCast(
- ArrayFromJSON(integer_type, "[0, 7, null, 100, 99]"),
- ArrayFromJSON(decimal_type, R"(["0.00", "7.00", null, "100.00",
"99.00"])"));
+ if (decimal_type->bit_width() > integer_type->bit_width()) {
+ CheckCast(
+ ArrayFromJSON(integer_type, "[0, 7, null, 100, 99]"),
+ ArrayFromJSON(decimal_type, R"(["0.00", "7.00", null, "100.00",
"99.00"])"));
+ }
}
}
@@ -652,6 +808,12 @@ TEST(Cast, IntegerToDecimal) {
{
CastOptions options;
+ options.to_type = decimal32(9, 3);
+ CheckCastFails(ArrayFromJSON(int32(), "[0]"), options);
+
+ options.to_type = decimal64(18, 3);
+ CheckCastFails(ArrayFromJSON(int64(), "[0]"), options);
+
options.to_type = decimal128(5, 3);
CheckCastFails(ArrayFromJSON(int8(), "[0]"), options);
@@ -660,6 +822,166 @@ TEST(Cast, IntegerToDecimal) {
}
}
+TEST(Cast, Decimal32ToDecimal32) {
+ CastOptions options;
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto no_truncation = ArrayFromJSON(decimal32(9, 5), R"([
+ "02.00000",
+ "30.00000",
+ "22.00000",
+ "-121.00000",
+ null])");
+ auto expected = ArrayFromJSON(decimal32(9, 0), R"([
+ "02.",
+ "30.",
+ "22.",
+ "-121.",
+ null])");
+
+ CheckCast(no_truncation, expected, options);
+ CheckCast(expected, no_truncation, options);
+ }
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ // Same scale, different precision
+ auto d_5_2 = ArrayFromJSON(decimal32(5, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_4_2 = ArrayFromJSON(decimal32(4, 2), R"([
+ "12.34",
+ "0.56"])");
+
+ CheckCast(d_5_2, d_4_2, options);
+ CheckCast(d_4_2, d_5_2, options);
+ }
+
+ auto d_9_5 = ArrayFromJSON(decimal32(9, 5), R"([
+ "-02.12345",
+ "30.12345",
+ null])");
+
+ auto d_6_0 = ArrayFromJSON(decimal32(6, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d_9_5_roundtripped = ArrayFromJSON(decimal32(9, 5), R"([
+ "-02.00000",
+ "30.00000",
+ null])");
+
+ // Rescale which leads to truncation
+ options.allow_decimal_truncate = true;
+ CheckCast(d_9_5, d_6_0, options);
+ CheckCast(d_6_0, d_9_5_roundtripped, options);
+
+ options.allow_decimal_truncate = false;
+ options.to_type = d_6_0->type();
+ CheckCastFails(d_9_5, options);
+ CheckCast(d_6_0, d_9_5_roundtripped, options);
+
+ // Precision loss without rescale leads to truncation
+ auto d_4_2 = ArrayFromJSON(decimal32(4, 2), R"(["12.34"])");
+ for (auto expected : {
+ ArrayFromJSON(decimal32(3, 2), R"(["12.34"])"),
+ ArrayFromJSON(decimal32(4, 3), R"(["12.340"])"),
+ ArrayFromJSON(decimal32(2, 1), R"(["12.3"])"),
+ }) {
+ options.allow_decimal_truncate = true;
+ ASSERT_OK_AND_ASSIGN(auto invalid, Cast(d_4_2, expected->type(), options));
+ ASSERT_RAISES(Invalid, invalid.make_array()->ValidateFull());
+
+ options.allow_decimal_truncate = false;
+ options.to_type = expected->type();
+ CheckCastFails(d_4_2, options);
+ }
+}
+
+TEST(Cast, Decimal64ToDecimal64) {
+ CastOptions options;
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto no_truncation = ArrayFromJSON(decimal64(18, 10), R"([
+ "02.0000000000",
+ "30.0000000000",
+ "22.0000000000",
+ "-121.0000000000",
+ null])");
+ auto expected = ArrayFromJSON(decimal64(9, 0), R"([
+ "02.",
+ "30.",
+ "22.",
+ "-121.",
+ null])");
+
+ CheckCast(no_truncation, expected, options);
+ CheckCast(expected, no_truncation, options);
+ }
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ // Same scale, different precision
+ auto d_5_2 = ArrayFromJSON(decimal64(5, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_4_2 = ArrayFromJSON(decimal64(4, 2), R"([
+ "12.34",
+ "0.56"])");
+
+ CheckCast(d_5_2, d_4_2, options);
+ CheckCast(d_4_2, d_5_2, options);
+ }
+
+ auto d_18_10 = ArrayFromJSON(decimal64(18, 10), R"([
+ "-02.1234567890",
+ "30.1234567890",
+ null])");
+
+ auto d_12_0 = ArrayFromJSON(decimal64(12, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d_18_10_roundtripped = ArrayFromJSON(decimal64(18, 10), R"([
+ "-02.0000000000",
+ "30.0000000000",
+ null])");
+
+ // Rescale which leads to truncation
+ options.allow_decimal_truncate = true;
+ CheckCast(d_18_10, d_12_0, options);
+ CheckCast(d_12_0, d_18_10_roundtripped, options);
+
+ options.allow_decimal_truncate = false;
+ options.to_type = d_12_0->type();
+ CheckCastFails(d_18_10, options);
+ CheckCast(d_12_0, d_18_10_roundtripped, options);
+
+ // Precision loss without rescale leads to truncation
+ auto d_4_2 = ArrayFromJSON(decimal64(4, 2), R"(["12.34"])");
+ for (auto expected : {
+ ArrayFromJSON(decimal64(3, 2), R"(["12.34"])"),
+ ArrayFromJSON(decimal64(4, 3), R"(["12.340"])"),
+ ArrayFromJSON(decimal64(2, 1), R"(["12.3"])"),
+ }) {
+ options.allow_decimal_truncate = true;
+ ASSERT_OK_AND_ASSIGN(auto invalid, Cast(d_4_2, expected->type(), options));
+ ASSERT_RAISES(Invalid, invalid.make_array()->ValidateFull());
+
+ options.allow_decimal_truncate = false;
+ options.to_type = expected->type();
+ CheckCastFails(d_4_2, options);
+ }
+}
+
TEST(Cast, Decimal128ToDecimal128) {
CastOptions options;
@@ -820,19 +1142,19 @@ TEST(Cast, Decimal256ToDecimal256) {
}
}
-TEST(Cast, Decimal128ToDecimal256) {
+TEST(Cast, Decimal32ToDecimal64) {
CastOptions options;
for (bool allow_decimal_truncate : {false, true}) {
options.allow_decimal_truncate = allow_decimal_truncate;
- auto no_truncation = ArrayFromJSON(decimal128(38, 10), R"([
- "02.0000000000",
- "30.0000000000",
- "22.0000000000",
- "-121.0000000000",
+ auto no_truncation = ArrayFromJSON(decimal32(9, 5), R"([
+ "02.00000",
+ "30.00000",
+ "22.00000",
+ "-121.00000",
null])");
- auto expected = ArrayFromJSON(decimal256(48, 0), R"([
+ auto expected = ArrayFromJSON(decimal64(16, 0), R"([
"02.",
"30.",
"22.",
@@ -846,47 +1168,731 @@ TEST(Cast, Decimal128ToDecimal256) {
options.allow_decimal_truncate = allow_decimal_truncate;
// Same scale, different precision
- auto d_5_2 = ArrayFromJSON(decimal128(5, 2), R"([
+ auto d_5_2 = ArrayFromJSON(decimal32(5, 2), R"([
"12.34",
"0.56"])");
- auto d_4_2 = ArrayFromJSON(decimal256(4, 2), R"([
+ auto d_4_2 = ArrayFromJSON(decimal64(4, 2), R"([
"12.34",
"0.56"])");
- auto d_40_2 = ArrayFromJSON(decimal256(40, 2), R"([
+ auto d_16_2 = ArrayFromJSON(decimal64(16, 2), R"([
"12.34",
"0.56"])");
CheckCast(d_5_2, d_4_2, options);
- CheckCast(d_5_2, d_40_2, options);
+ CheckCast(d_5_2, d_16_2, options);
}
- auto d128_38_10 = ArrayFromJSON(decimal128(38, 10), R"([
- "-02.1234567890",
- "30.1234567890",
+ auto d32_7_5 = ArrayFromJSON(decimal32(7, 5), R"([
+ "-02.12345",
+ "30.12345",
null])");
- auto d128_28_0 = ArrayFromJSON(decimal128(28, 0), R"([
+ auto d32_9_0 = ArrayFromJSON(decimal32(9, 0), R"([
"-02.",
"30.",
null])");
- auto d256_28_0 = ArrayFromJSON(decimal256(28, 0), R"([
+ auto d64_14_0 = ArrayFromJSON(decimal64(14, 0), R"([
"-02.",
"30.",
null])");
- auto d256_38_10_roundtripped = ArrayFromJSON(decimal256(38, 10), R"([
+ auto d64_18_10_roundtripped = ArrayFromJSON(decimal64(18, 10), R"([
"-02.0000000000",
"30.0000000000",
null])");
// Rescale which leads to truncation
options.allow_decimal_truncate = true;
- CheckCast(d128_38_10, d256_28_0, options);
- CheckCast(d128_28_0, d256_38_10_roundtripped, options);
+ CheckCast(d32_7_5, d64_14_0, options);
+ CheckCast(d32_9_0, d64_18_10_roundtripped, options);
options.allow_decimal_truncate = false;
- options.to_type = d256_28_0->type();
+ options.to_type = d64_14_0->type();
+ CheckCastFails(d32_7_5, options);
+ CheckCast(d32_9_0, d64_18_10_roundtripped, options);
+
+ // Precision loss without rescale leads to truncation
+ auto d32_4_2 = ArrayFromJSON(decimal32(4, 2), R"(["12.34"])");
+ for (auto expected : {
+ ArrayFromJSON(decimal64(3, 2), R"(["12.34"])"),
+ ArrayFromJSON(decimal64(4, 3), R"(["12.340"])"),
+ ArrayFromJSON(decimal64(2, 1), R"(["12.3"])"),
+ }) {
+ options.allow_decimal_truncate = true;
+ ASSERT_OK_AND_ASSIGN(auto invalid, Cast(d32_4_2, expected->type(),
options));
+ ASSERT_RAISES(Invalid, invalid.make_array()->ValidateFull());
+
+ options.allow_decimal_truncate = false;
+ options.to_type = expected->type();
+ CheckCastFails(d32_4_2, options);
+ }
+}
+
+TEST(Cast, Decimal32ToDecimal128) {
+ CastOptions options;
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto no_truncation = ArrayFromJSON(decimal32(9, 5), R"([
+ "02.00000",
+ "30.00000",
+ "22.00000",
+ "-121.00000",
+ null])");
+ auto expected = ArrayFromJSON(decimal128(16, 0), R"([
+ "02.",
+ "30.",
+ "22.",
+ "-121.",
+ null])");
+
+ CheckCast(no_truncation, expected, options);
+ }
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ // Same scale, different precision
+ auto d_5_2 = ArrayFromJSON(decimal32(5, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_4_2 = ArrayFromJSON(decimal128(4, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_16_2 = ArrayFromJSON(decimal128(16, 2), R"([
+ "12.34",
+ "0.56"])");
+
+ CheckCast(d_5_2, d_4_2, options);
+ CheckCast(d_5_2, d_16_2, options);
+ }
+
+ auto d32_7_5 = ArrayFromJSON(decimal32(7, 5), R"([
+ "-02.12345",
+ "30.12345",
+ null])");
+
+ auto d32_9_0 = ArrayFromJSON(decimal32(9, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d128_14_0 = ArrayFromJSON(decimal128(14, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d128_38_10_roundtripped = ArrayFromJSON(decimal128(38, 10), R"([
+ "-02.0000000000",
+ "30.0000000000",
+ null])");
+
+ // Rescale which leads to truncation
+ options.allow_decimal_truncate = true;
+ CheckCast(d32_7_5, d128_14_0, options);
+ CheckCast(d32_9_0, d128_38_10_roundtripped, options);
+
+ options.allow_decimal_truncate = false;
+ options.to_type = d128_14_0->type();
+ CheckCastFails(d32_7_5, options);
+ CheckCast(d32_9_0, d128_38_10_roundtripped, options);
+
+ // Precision loss without rescale leads to truncation
+ auto d32_4_2 = ArrayFromJSON(decimal32(4, 2), R"(["12.34"])");
+ for (auto expected : {
+ ArrayFromJSON(decimal128(3, 2), R"(["12.34"])"),
+ ArrayFromJSON(decimal128(4, 3), R"(["12.340"])"),
+ ArrayFromJSON(decimal128(2, 1), R"(["12.3"])"),
+ }) {
+ options.allow_decimal_truncate = true;
+ ASSERT_OK_AND_ASSIGN(auto invalid, Cast(d32_4_2, expected->type(),
options));
+ ASSERT_RAISES(Invalid, invalid.make_array()->ValidateFull());
+
+ options.allow_decimal_truncate = false;
+ options.to_type = expected->type();
+ CheckCastFails(d32_4_2, options);
+ }
+}
+
+TEST(Cast, Decimal32ToDecimal256) {
+ CastOptions options;
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto no_truncation = ArrayFromJSON(decimal32(9, 5), R"([
+ "02.00000",
+ "30.00000",
+ "22.00000",
+ "-121.00000",
+ null])");
+ auto expected = ArrayFromJSON(decimal256(16, 0), R"([
+ "02.",
+ "30.",
+ "22.",
+ "-121.",
+ null])");
+
+ CheckCast(no_truncation, expected, options);
+ }
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ // Same scale, different precision
+ auto d_5_2 = ArrayFromJSON(decimal32(5, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_4_2 = ArrayFromJSON(decimal256(4, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_16_2 = ArrayFromJSON(decimal256(16, 2), R"([
+ "12.34",
+ "0.56"])");
+
+ CheckCast(d_5_2, d_4_2, options);
+ CheckCast(d_5_2, d_16_2, options);
+ }
+
+ auto d32_7_5 = ArrayFromJSON(decimal32(7, 5), R"([
+ "-02.12345",
+ "30.12345",
+ null])");
+
+ auto d32_9_0 = ArrayFromJSON(decimal32(9, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d256_14_0 = ArrayFromJSON(decimal256(14, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d256_76_10_roundtripped = ArrayFromJSON(decimal256(76, 10), R"([
+ "-02.0000000000",
+ "30.0000000000",
+ null])");
+
+ // Rescale which leads to truncation
+ options.allow_decimal_truncate = true;
+ CheckCast(d32_7_5, d256_14_0, options);
+ CheckCast(d32_9_0, d256_76_10_roundtripped, options);
+
+ options.allow_decimal_truncate = false;
+ options.to_type = d256_14_0->type();
+ CheckCastFails(d32_7_5, options);
+ CheckCast(d32_9_0, d256_76_10_roundtripped, options);
+
+ // Precision loss without rescale leads to truncation
+ auto d32_4_2 = ArrayFromJSON(decimal32(4, 2), R"(["12.34"])");
+ for (auto expected : {
+ ArrayFromJSON(decimal256(3, 2), R"(["12.34"])"),
+ ArrayFromJSON(decimal256(4, 3), R"(["12.340"])"),
+ ArrayFromJSON(decimal256(2, 1), R"(["12.3"])"),
+ }) {
+ options.allow_decimal_truncate = true;
+ ASSERT_OK_AND_ASSIGN(auto invalid, Cast(d32_4_2, expected->type(),
options));
+ ASSERT_RAISES(Invalid, invalid.make_array()->ValidateFull());
+
+ options.allow_decimal_truncate = false;
+ options.to_type = expected->type();
+ CheckCastFails(d32_4_2, options);
+ }
+}
+
+TEST(Cast, Decimal64ToDecimal32) {
+ CastOptions options;
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto no_truncation = ArrayFromJSON(decimal64(18, 5), R"([
+ "02.00000",
+ "30.00000",
+ "22.00000",
+ "-121.00000",
+ null])");
+ auto expected = ArrayFromJSON(decimal32(9, 0), R"([
+ "02.",
+ "30.",
+ "22.",
+ "-121.",
+ null])");
+
+ CheckCast(no_truncation, expected, options);
+ }
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ // Same scale, different precision
+ auto d_12_2 = ArrayFromJSON(decimal64(12, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_4_2 = ArrayFromJSON(decimal32(4, 2), R"([
+ "12.34",
+ "0.56"])");
+
+ CheckCast(d_12_2, d_4_2, options);
+ }
+
+ auto d64_15_10 = ArrayFromJSON(decimal64(15, 5), R"([
+ "-02.12345",
+ "30.12345",
+ null])");
+
+ auto d64_12_0 = ArrayFromJSON(decimal64(12, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d32_6_0 = ArrayFromJSON(decimal32(6, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d32_9_5_roundtripped = ArrayFromJSON(decimal32(9, 5), R"([
+ "-02.00000",
+ "30.00000",
+ null])");
+
+ // Rescale which leads to truncation
+ options.allow_decimal_truncate = true;
+ CheckCast(d64_15_10, d32_6_0, options);
+ CheckCast(d64_12_0, d32_9_5_roundtripped, options);
+
+ options.allow_decimal_truncate = false;
+ options.to_type = d32_6_0->type();
+ CheckCastFails(d64_15_10, options);
+ CheckCast(d64_12_0, d32_9_5_roundtripped, options);
+
+ // Precision loss without rescale leads to truncation
+ auto d64_4_2 = ArrayFromJSON(decimal64(4, 2), R"(["12.34"])");
+ for (auto expected : {
+ ArrayFromJSON(decimal32(3, 2), R"(["12.34"])"),
+ ArrayFromJSON(decimal32(4, 3), R"(["12.340"])"),
+ ArrayFromJSON(decimal32(2, 1), R"(["12.3"])"),
+ }) {
+ options.allow_decimal_truncate = true;
+ ASSERT_OK_AND_ASSIGN(auto invalid, Cast(d64_4_2, expected->type(),
options));
+ ASSERT_RAISES(Invalid, invalid.make_array()->ValidateFull());
+
+ options.allow_decimal_truncate = false;
+ options.to_type = expected->type();
+ CheckCastFails(d64_4_2, options);
+ }
+}
+
+TEST(Cast, Decimal64ToDecimal128) {
+ CastOptions options;
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto no_truncation = ArrayFromJSON(decimal64(18, 10), R"([
+ "02.0000000000",
+ "30.0000000000",
+ "22.0000000000",
+ "-121.0000000000",
+ null])");
+ auto expected = ArrayFromJSON(decimal128(28, 0), R"([
+ "02.",
+ "30.",
+ "22.",
+ "-121.",
+ null])");
+
+ CheckCast(no_truncation, expected, options);
+ }
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ // Same scale, different precision
+ auto d_5_2 = ArrayFromJSON(decimal64(5, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_4_2 = ArrayFromJSON(decimal128(4, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_16_2 = ArrayFromJSON(decimal128(16, 2), R"([
+ "12.34",
+ "0.56"])");
+
+ CheckCast(d_5_2, d_4_2, options);
+ CheckCast(d_5_2, d_16_2, options);
+ }
+
+ auto d64_16_10 = ArrayFromJSON(decimal64(16, 10), R"([
+ "-02.1234567890",
+ "30.1234567890",
+ null])");
+
+ auto d64_18_0 = ArrayFromJSON(decimal64(18, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d128_14_0 = ArrayFromJSON(decimal128(14, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d128_38_10_roundtripped = ArrayFromJSON(decimal128(38, 10), R"([
+ "-02.0000000000",
+ "30.0000000000",
+ null])");
+
+ // Rescale which leads to truncation
+ options.allow_decimal_truncate = true;
+ CheckCast(d64_16_10, d128_14_0, options);
+ CheckCast(d64_18_0, d128_38_10_roundtripped, options);
+
+ options.allow_decimal_truncate = false;
+ options.to_type = d128_14_0->type();
+ CheckCastFails(d64_16_10, options);
+ CheckCast(d64_18_0, d128_38_10_roundtripped, options);
+
+ // Precision loss without rescale leads to truncation
+ auto d64_4_2 = ArrayFromJSON(decimal64(4, 2), R"(["12.34"])");
+ for (auto expected : {
+ ArrayFromJSON(decimal128(3, 2), R"(["12.34"])"),
+ ArrayFromJSON(decimal128(4, 3), R"(["12.340"])"),
+ ArrayFromJSON(decimal128(2, 1), R"(["12.3"])"),
+ }) {
+ options.allow_decimal_truncate = true;
+ ASSERT_OK_AND_ASSIGN(auto invalid, Cast(d64_4_2, expected->type(),
options));
+ ASSERT_RAISES(Invalid, invalid.make_array()->ValidateFull());
+
+ options.allow_decimal_truncate = false;
+ options.to_type = expected->type();
+ CheckCastFails(d64_4_2, options);
+ }
+}
+
+TEST(Cast, Decimal64ToDecimal256) {
+ CastOptions options;
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto no_truncation = ArrayFromJSON(decimal64(18, 10), R"([
+ "02.0000000000",
+ "30.0000000000",
+ "22.0000000000",
+ "-121.0000000000",
+ null])");
+ auto expected = ArrayFromJSON(decimal256(16, 0), R"([
+ "02.",
+ "30.",
+ "22.",
+ "-121.",
+ null])");
+
+ CheckCast(no_truncation, expected, options);
+ }
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ // Same scale, different precision
+ auto d_5_2 = ArrayFromJSON(decimal64(5, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_4_2 = ArrayFromJSON(decimal256(4, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_16_2 = ArrayFromJSON(decimal256(16, 2), R"([
+ "12.34",
+ "0.56"])");
+
+ CheckCast(d_5_2, d_4_2, options);
+ CheckCast(d_5_2, d_16_2, options);
+ }
+
+ auto d64_16_10 = ArrayFromJSON(decimal64(16, 10), R"([
+ "-02.1234567890",
+ "30.1234567890",
+ null])");
+
+ auto d64_18_0 = ArrayFromJSON(decimal64(18, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d256_14_0 = ArrayFromJSON(decimal256(14, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d256_76_10_roundtripped = ArrayFromJSON(decimal256(76, 10), R"([
+ "-02.0000000000",
+ "30.0000000000",
+ null])");
+
+ // Rescale which leads to truncation
+ options.allow_decimal_truncate = true;
+ CheckCast(d64_16_10, d256_14_0, options);
+ CheckCast(d64_18_0, d256_76_10_roundtripped, options);
+
+ options.allow_decimal_truncate = false;
+ options.to_type = d256_14_0->type();
+ CheckCastFails(d64_16_10, options);
+ CheckCast(d64_18_0, d256_76_10_roundtripped, options);
+
+ // Precision loss without rescale leads to truncation
+ auto d64_4_2 = ArrayFromJSON(decimal64(4, 2), R"(["12.34"])");
+ for (auto expected : {
+ ArrayFromJSON(decimal256(3, 2), R"(["12.34"])"),
+ ArrayFromJSON(decimal256(4, 3), R"(["12.340"])"),
+ ArrayFromJSON(decimal256(2, 1), R"(["12.3"])"),
+ }) {
+ options.allow_decimal_truncate = true;
+ ASSERT_OK_AND_ASSIGN(auto invalid, Cast(d64_4_2, expected->type(),
options));
+ ASSERT_RAISES(Invalid, invalid.make_array()->ValidateFull());
+
+ options.allow_decimal_truncate = false;
+ options.to_type = expected->type();
+ CheckCastFails(d64_4_2, options);
+ }
+}
+
+TEST(Cast, Decimal128ToDecimal32) {
+ CastOptions options;
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto no_truncation = ArrayFromJSON(decimal128(26, 5), R"([
+ "02.00000",
+ "30.00000",
+ "22.00000",
+ "-121.00000",
+ null])");
+ auto expected = ArrayFromJSON(decimal32(9, 0), R"([
+ "02.",
+ "30.",
+ "22.",
+ "-121.",
+ null])");
+
+ CheckCast(no_truncation, expected, options);
+ }
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ // Same scale, different precision
+ auto d_28_2 = ArrayFromJSON(decimal128(28, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_4_2 = ArrayFromJSON(decimal32(4, 2), R"([
+ "12.34",
+ "0.56"])");
+
+ CheckCast(d_28_2, d_4_2, options);
+ }
+
+ auto d128_28_5 = ArrayFromJSON(decimal128(28, 5), R"([
+ "-02.12345",
+ "30.12345",
+ null])");
+
+ auto d128_22_0 = ArrayFromJSON(decimal128(22, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d32_7_0 = ArrayFromJSON(decimal32(7, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d32_9_5_roundtripped = ArrayFromJSON(decimal32(9, 5), R"([
+ "-02.00000",
+ "30.00000",
+ null])");
+
+ // Rescale which leads to truncation
+ options.allow_decimal_truncate = true;
+ CheckCast(d128_28_5, d32_7_0, options);
+ CheckCast(d128_22_0, d32_9_5_roundtripped, options);
+
+ options.allow_decimal_truncate = false;
+ options.to_type = d32_7_0->type();
+ CheckCastFails(d128_28_5, options);
+ CheckCast(d128_22_0, d32_9_5_roundtripped, options);
+
+ // Precision loss without rescale leads to truncation
+ auto d128_4_2 = ArrayFromJSON(decimal128(4, 2), R"(["12.34"])");
+ for (auto expected : {
+ ArrayFromJSON(decimal32(3, 2), R"(["12.34"])"),
+ ArrayFromJSON(decimal32(4, 3), R"(["12.340"])"),
+ ArrayFromJSON(decimal32(2, 1), R"(["12.3"])"),
+ }) {
+ options.allow_decimal_truncate = true;
+ ASSERT_OK_AND_ASSIGN(auto invalid, Cast(d128_4_2, expected->type(),
options));
+ ASSERT_RAISES(Invalid, invalid.make_array()->ValidateFull());
+
+ options.allow_decimal_truncate = false;
+ options.to_type = expected->type();
+ CheckCastFails(d128_4_2, options);
+ }
+}
+
+TEST(Cast, Decimal128ToDecimal64) {
+ CastOptions options;
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto no_truncation = ArrayFromJSON(decimal128(26, 10), R"([
+ "02.0000000000",
+ "30.0000000000",
+ "22.0000000000",
+ "-121.0000000000",
+ null])");
+ auto expected = ArrayFromJSON(decimal64(15, 0), R"([
+ "02.",
+ "30.",
+ "22.",
+ "-121.",
+ null])");
+
+ CheckCast(no_truncation, expected, options);
+ }
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ // Same scale, different precision
+ auto d_28_2 = ArrayFromJSON(decimal128(28, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_4_2 = ArrayFromJSON(decimal64(4, 2), R"([
+ "12.34",
+ "0.56"])");
+
+ CheckCast(d_28_2, d_4_2, options);
+ }
+
+ auto d128_28_10 = ArrayFromJSON(decimal128(28, 10), R"([
+ "-02.1234567890",
+ "30.1234567890",
+ null])");
+
+ auto d128_22_0 = ArrayFromJSON(decimal128(22, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d64_12_0 = ArrayFromJSON(decimal64(12, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d64_18_10_roundtripped = ArrayFromJSON(decimal64(18, 10), R"([
+ "-02.0000000000",
+ "30.0000000000",
+ null])");
+
+ // Rescale which leads to truncation
+ options.allow_decimal_truncate = true;
+ CheckCast(d128_28_10, d64_12_0, options);
+ CheckCast(d128_22_0, d64_18_10_roundtripped, options);
+
+ options.allow_decimal_truncate = false;
+ options.to_type = d64_12_0->type();
+ CheckCastFails(d128_28_10, options);
+ CheckCast(d128_22_0, d64_18_10_roundtripped, options);
+
+ // Precision loss without rescale leads to truncation
+ auto d128_4_2 = ArrayFromJSON(decimal128(4, 2), R"(["12.34"])");
+ for (auto expected : {
+ ArrayFromJSON(decimal64(3, 2), R"(["12.34"])"),
+ ArrayFromJSON(decimal64(4, 3), R"(["12.340"])"),
+ ArrayFromJSON(decimal64(2, 1), R"(["12.3"])"),
+ }) {
+ options.allow_decimal_truncate = true;
+ ASSERT_OK_AND_ASSIGN(auto invalid, Cast(d128_4_2, expected->type(),
options));
+ ASSERT_RAISES(Invalid, invalid.make_array()->ValidateFull());
+
+ options.allow_decimal_truncate = false;
+ options.to_type = expected->type();
+ CheckCastFails(d128_4_2, options);
+ }
+}
+
+TEST(Cast, Decimal128ToDecimal256) {
+ CastOptions options;
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto no_truncation = ArrayFromJSON(decimal128(38, 10), R"([
+ "02.0000000000",
+ "30.0000000000",
+ "22.0000000000",
+ "-121.0000000000",
+ null])");
+ auto expected = ArrayFromJSON(decimal256(48, 0), R"([
+ "02.",
+ "30.",
+ "22.",
+ "-121.",
+ null])");
+
+ CheckCast(no_truncation, expected, options);
+ }
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ // Same scale, different precision
+ auto d_5_2 = ArrayFromJSON(decimal128(5, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_4_2 = ArrayFromJSON(decimal256(4, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_40_2 = ArrayFromJSON(decimal256(40, 2), R"([
+ "12.34",
+ "0.56"])");
+
+ CheckCast(d_5_2, d_4_2, options);
+ CheckCast(d_5_2, d_40_2, options);
+ }
+
+ auto d128_38_10 = ArrayFromJSON(decimal128(38, 10), R"([
+ "-02.1234567890",
+ "30.1234567890",
+ null])");
+
+ auto d128_28_0 = ArrayFromJSON(decimal128(28, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d256_28_0 = ArrayFromJSON(decimal256(28, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d256_38_10_roundtripped = ArrayFromJSON(decimal256(38, 10), R"([
+ "-02.0000000000",
+ "30.0000000000",
+ null])");
+
+ // Rescale which leads to truncation
+ options.allow_decimal_truncate = true;
+ CheckCast(d128_38_10, d256_28_0, options);
+ CheckCast(d128_28_0, d256_38_10_roundtripped, options);
+
+ options.allow_decimal_truncate = false;
+ options.to_type = d256_28_0->type();
CheckCastFails(d128_38_10, options);
CheckCast(d128_28_0, d256_38_10_roundtripped, options);
@@ -907,6 +1913,172 @@ TEST(Cast, Decimal128ToDecimal256) {
}
}
+TEST(Cast, Decimal256ToDecimal32) {
+ CastOptions options;
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto no_truncation = ArrayFromJSON(decimal256(42, 5), R"([
+ "02.00000",
+ "30.00000",
+ "22.00000",
+ "-121.00000",
+ null])");
+ auto expected = ArrayFromJSON(decimal32(9, 0), R"([
+ "02.",
+ "30.",
+ "22.",
+ "-121.",
+ null])");
+
+ CheckCast(no_truncation, expected, options);
+ }
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ // Same scale, different precision
+ auto d_28_2 = ArrayFromJSON(decimal256(42, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_4_2 = ArrayFromJSON(decimal32(4, 2), R"([
+ "12.34",
+ "0.56"])");
+
+ CheckCast(d_28_2, d_4_2, options);
+ }
+
+ auto d256_52_5 = ArrayFromJSON(decimal256(52, 5), R"([
+ "-02.12345",
+ "30.12345",
+ null])");
+
+ auto d256_42_0 = ArrayFromJSON(decimal256(42, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d32_7_0 = ArrayFromJSON(decimal32(7, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d32_9_5_roundtripped = ArrayFromJSON(decimal32(9, 5), R"([
+ "-02.00000",
+ "30.00000",
+ null])");
+
+ // Rescale which leads to truncation
+ options.allow_decimal_truncate = true;
+ CheckCast(d256_52_5, d32_7_0, options);
+ CheckCast(d256_42_0, d32_9_5_roundtripped, options);
+
+ options.allow_decimal_truncate = false;
+ options.to_type = d32_7_0->type();
+ CheckCastFails(d256_52_5, options);
+ CheckCast(d256_42_0, d32_9_5_roundtripped, options);
+
+ // Precision loss without rescale leads to truncation
+ auto d256_4_2 = ArrayFromJSON(decimal256(4, 2), R"(["12.34"])");
+ for (auto expected : {
+ ArrayFromJSON(decimal32(3, 2), R"(["12.34"])"),
+ ArrayFromJSON(decimal32(4, 3), R"(["12.340"])"),
+ ArrayFromJSON(decimal32(2, 1), R"(["12.3"])"),
+ }) {
+ options.allow_decimal_truncate = true;
+ ASSERT_OK_AND_ASSIGN(auto invalid, Cast(d256_4_2, expected->type(),
options));
+ ASSERT_RAISES(Invalid, invalid.make_array()->ValidateFull());
+
+ options.allow_decimal_truncate = false;
+ options.to_type = expected->type();
+ CheckCastFails(d256_4_2, options);
+ }
+}
+
+TEST(Cast, Decimal256ToDecimal64) {
+ CastOptions options;
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ auto no_truncation = ArrayFromJSON(decimal256(42, 10), R"([
+ "02.0000000000",
+ "30.0000000000",
+ "22.0000000000",
+ "-121.0000000000",
+ null])");
+ auto expected = ArrayFromJSON(decimal64(15, 0), R"([
+ "02.",
+ "30.",
+ "22.",
+ "-121.",
+ null])");
+
+ CheckCast(no_truncation, expected, options);
+ }
+
+ for (bool allow_decimal_truncate : {false, true}) {
+ options.allow_decimal_truncate = allow_decimal_truncate;
+
+ // Same scale, different precision
+ auto d_42_2 = ArrayFromJSON(decimal256(42, 2), R"([
+ "12.34",
+ "0.56"])");
+ auto d_4_2 = ArrayFromJSON(decimal64(4, 2), R"([
+ "12.34",
+ "0.56"])");
+
+ CheckCast(d_42_2, d_4_2, options);
+ }
+
+ auto d256_52_10 = ArrayFromJSON(decimal256(52, 10), R"([
+ "-02.1234567890",
+ "30.1234567890",
+ null])");
+
+ auto d256_42_0 = ArrayFromJSON(decimal256(42, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d64_12_0 = ArrayFromJSON(decimal64(12, 0), R"([
+ "-02.",
+ "30.",
+ null])");
+
+ auto d64_18_10_roundtripped = ArrayFromJSON(decimal64(18, 10), R"([
+ "-02.0000000000",
+ "30.0000000000",
+ null])");
+
+ // Rescale which leads to truncation
+ options.allow_decimal_truncate = true;
+ CheckCast(d256_52_10, d64_12_0, options);
+ CheckCast(d256_42_0, d64_18_10_roundtripped, options);
+
+ options.allow_decimal_truncate = false;
+ options.to_type = d64_12_0->type();
+ CheckCastFails(d256_52_10, options);
+ CheckCast(d256_42_0, d64_18_10_roundtripped, options);
+
+ // Precision loss without rescale leads to truncation
+ auto d256_4_2 = ArrayFromJSON(decimal256(4, 2), R"(["12.34"])");
+ for (auto expected : {
+ ArrayFromJSON(decimal64(3, 2), R"(["12.34"])"),
+ ArrayFromJSON(decimal64(4, 3), R"(["12.340"])"),
+ ArrayFromJSON(decimal64(2, 1), R"(["12.3"])"),
+ }) {
+ options.allow_decimal_truncate = true;
+ ASSERT_OK_AND_ASSIGN(auto invalid, Cast(d256_4_2, expected->type(),
options));
+ ASSERT_RAISES(Invalid, invalid.make_array()->ValidateFull());
+
+ options.allow_decimal_truncate = false;
+ options.to_type = expected->type();
+ CheckCastFails(d256_4_2, options);
+ }
+}
+
TEST(Cast, Decimal256ToDecimal128) {
CastOptions options;
@@ -992,7 +2164,8 @@ TEST(Cast, Decimal256ToDecimal128) {
TEST(Cast, FloatingToDecimal) {
for (auto float_type : {float32(), float64()}) {
- for (auto decimal_type : {decimal128(5, 2), decimal256(5, 2)}) {
+ for (auto decimal_type :
+ {decimal32(5, 2), decimal64(5, 2), decimal128(5, 2), decimal256(5,
2)}) {
CheckCast(
ArrayFromJSON(float_type, "[0.0, null, 123.45, 123.456, 999.994]"),
ArrayFromJSON(decimal_type, R"(["0.00", null, "123.45", "123.46",
"999.99"])"));
@@ -1036,7 +2209,8 @@ TEST(Cast, FloatingToDecimal) {
TEST(Cast, DecimalToFloating) {
for (auto float_type : {float32(), float64()}) {
- for (auto decimal_type : {decimal128(5, 2), decimal256(5, 2)}) {
+ for (auto decimal_type :
+ {decimal32(5, 2), decimal64(5, 2), decimal128(5, 2), decimal256(5,
2)}) {
CheckCast(ArrayFromJSON(decimal_type, R"(["0.00", null, "123.45",
"999.99"])"),
ArrayFromJSON(float_type, "[0.0, null, 123.45, 999.99]"));
}
@@ -1048,7 +2222,8 @@ TEST(Cast, DecimalToFloating) {
TEST(Cast, DecimalToString) {
for (auto string_type : {utf8(), utf8_view(), large_utf8()}) {
- for (auto decimal_type : {decimal128(5, 2), decimal256(5, 2)}) {
+ for (auto decimal_type :
+ {decimal32(5, 2), decimal64(5, 2), decimal128(5, 2), decimal256(5,
2)}) {
CheckCast(ArrayFromJSON(decimal_type, R"(["0.00", null, "123.45",
"999.99"])"),
ArrayFromJSON(string_type, R"(["0.00", null, "123.45",
"999.99"])"));
}
@@ -1960,7 +3135,8 @@ TEST(Cast, StringToFloating) {
TEST(Cast, StringToDecimal) {
for (auto string_type : {utf8(), large_utf8()}) {
- for (auto decimal_type : {decimal128(5, 2), decimal256(5, 2)}) {
+ for (auto decimal_type :
+ {decimal32(5, 2), decimal64(5, 2), decimal128(5, 2), decimal256(5,
2)}) {
auto strings =
ArrayFromJSON(string_type, R"(["0.01", null, "127.32", "200.43",
"0.54"])");
auto decimals =
diff --git a/cpp/src/arrow/util/basic_decimal.h
b/cpp/src/arrow/util/basic_decimal.h
index 9c1f2e479c..b5404bb7bc 100644
--- a/cpp/src/arrow/util/basic_decimal.h
+++ b/cpp/src/arrow/util/basic_decimal.h
@@ -739,6 +739,16 @@ class ARROW_EXPORT BasicDecimal256 : public
GenericBasicDecimal<BasicDecimal256,
{value.low_bits(), static_cast<uint64_t>(value.high_bits()),
SignExtend(value.high_bits()), SignExtend(value.high_bits())})) {}
+ explicit BasicDecimal256(const BasicDecimal64& value) noexcept
+ : BasicDecimal256(bit_util::little_endian::ToNative<uint64_t, 4>(
+ {value.low_bits(), SignExtend(value.value()),
SignExtend(value.value()),
+ SignExtend(value.value())})) {}
+
+ explicit BasicDecimal256(const BasicDecimal32& value) noexcept
+ : BasicDecimal256(bit_util::little_endian::ToNative<uint64_t, 4>(
+ {value.low_bits(), SignExtend(value.value()),
SignExtend(value.value()),
+ SignExtend(value.value())})) {}
+
/// \brief Negate the current value (in-place)
BasicDecimal256& Negate();