You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by gi...@apache.org on 2026/10/07 08:55:03 UTC
(arrow-rs) branch gh-readonly-queue/main/pr-11420-3c5bc5f4ce8f13f0974e5f01fdbed965d206172e created (now 9d81159089)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a change to branch gh-readonly-queue/main/pr-11420-3c5bc5f4ce8f13f0974e5f01fdbed965d206172e
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
at 9d81159089 fix(arrow-arith): keep decimal division precision valid (#11420)
This branch includes the following new commits:
new 9d81159089 fix(arrow-arith): keep decimal division precision valid (#11420)
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(arrow-rs) 01/01: fix(arrow-arith): keep decimal division precision valid (#11420)
Posted by gi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-11420-3c5bc5f4ce8f13f0974e5f01fdbed965d206172e
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
commit 9d811590898b00993654275febd58e1e3b09776a
Author: Geonho <mn...@naver.com>
AuthorDate: Wed Oct 7 08:54:42 2026 +0000
fix(arrow-arith): keep decimal division precision valid (#11420)
# Which issue does this PR close?
Follow-up to #10996.
# Rationale for this change
Decimal division can reject representable results when the divisor has a
negative scale. For example, `0.1 / 10000` with input types
`Decimal32(1, 1)` and `Decimal32(1, -4)` should produce `0.00001` at
scale 5. Instead, the inferred precision is 1, and the result fails with
`scale 5 is greater than precision 1`.
# What changes are included in this PR?
Calculate the inferred precision in a wider signed integer, then bound
it by the minimum valid precision and the decimal type's maximum. The
lower bound is 1 or the output scale, whichever is greater. Scale
selection and coefficient arithmetic stay unchanged.
# Are these changes tested?
Two regressions cover all four decimal widths, separating the
positive-scale constraint from the minimum precision of 1. They compare
both coefficients and metadata, including signed values, zero, nulls and
adjacent cases that already work.
# Are there any significant user-facing changes?
When the inferred precision was negative, the unsigned conversion could
give a zero quotient the type's maximum precision. These results now use
the minimum valid precision; their coefficients and scale are unchanged.
AI assistance was used for the implementation, regression tests and this
description.
---
arrow-arith/src/numeric.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 70 insertions(+), 1 deletion(-)
diff --git a/arrow-arith/src/numeric.rs b/arrow-arith/src/numeric.rs
index 170d013d6d..d1d0c6435c 100644
--- a/arrow-arith/src/numeric.rs
+++ b/arrow-arith/src/numeric.rs
@@ -1116,7 +1116,10 @@ fn decimal_op<T: DecimalType>(
let mul_pow = result_scale - s1 + s2;
// p1 - s1 + s2 + result_scale
- let result_precision = (mul_pow.saturating_add(*p1 as i8) as u8).min(T::MAX_PRECISION);
+ // Precision must be positive and at least the output scale.
+ let result_precision = (mul_pow as i16 + *p1 as i16)
+ .clamp(result_scale.max(1) as i16, T::MAX_PRECISION as i16)
+ as u8;
let (l_mul, r_mul) = match mul_pow.cmp(&0) {
Ordering::Greater => (
@@ -1650,6 +1653,72 @@ mod tests {
);
}
+ #[test]
+ fn test_decimal_div_precision_at_least_scale() {
+ fn check<T: DecimalType>()
+ where
+ T::Native: From<i32>,
+ {
+ let a: PrimitiveArray<T> = [Some(1), Some(-1), Some(0), None]
+ .into_iter()
+ .map(|v| v.map(T::Native::from))
+ .collect();
+ let a = a.with_precision_and_scale(1, 1).unwrap();
+ for (scale, coefficient) in [(0, 10000), (-1, 1000), (-4, 1), (-5, 0), (-6, 0)] {
+ let b = PrimitiveArray::<T>::from_iter_values(
+ [1, 1, 1, 0].into_iter().map(T::Native::from),
+ )
+ .with_precision_and_scale(1, scale)
+ .unwrap();
+ let expected: PrimitiveArray<T> =
+ [Some(coefficient), Some(-coefficient), Some(0), None]
+ .into_iter()
+ .map(|v| v.map(T::Native::from))
+ .collect();
+ let expected = expected.with_precision_and_scale(5, 5).unwrap();
+ assert_eq!(div(&a, &b).unwrap().as_ref(), &expected);
+ }
+ }
+ check::<Decimal32Type>();
+ check::<Decimal64Type>();
+ check::<Decimal128Type>();
+ check::<Decimal256Type>();
+ }
+
+ #[test]
+ fn test_decimal_div_precision_at_least_one() {
+ fn check<T: DecimalType>()
+ where
+ T::Native: From<i32>,
+ {
+ for scale in [-4, -5] {
+ let a: PrimitiveArray<T> = [Some(1), Some(-1), Some(0), None]
+ .into_iter()
+ .map(|v| v.map(T::Native::from))
+ .collect();
+ let a = a.with_precision_and_scale(1, scale).unwrap();
+ for (divisor_scale, coefficient) in [(-4, 1), (-5, 0), (-6, 0)] {
+ let b = PrimitiveArray::<T>::from_iter_values(
+ [1, 1, 1, 0].into_iter().map(T::Native::from),
+ )
+ .with_precision_and_scale(1, divisor_scale)
+ .unwrap();
+ let expected: PrimitiveArray<T> =
+ [Some(coefficient), Some(-coefficient), Some(0), None]
+ .into_iter()
+ .map(|v| v.map(T::Native::from))
+ .collect();
+ let expected = expected.with_precision_and_scale(1, scale + 4).unwrap();
+ assert_eq!(div(&a, &b).unwrap().as_ref(), &expected);
+ }
+ }
+ }
+ check::<Decimal32Type>();
+ check::<Decimal64Type>();
+ check::<Decimal128Type>();
+ check::<Decimal256Type>();
+ }
+
#[test]
fn test_decimal256_div_wide_intermediate() {
// Dividing two scale-37 values needs l * 10^41, which is 79 digits and does not