Skip to main content

arrow_arith/
arithmetic.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Defines basic arithmetic kernels for `PrimitiveArrays`.
19//!
20//! These kernels can leverage SIMD if available on your system.  Currently no runtime
21//! detection is provided, you should enable the specific SIMD intrinsics using
22//! `RUSTFLAGS="-C target-feature=+avx2"` for example.  See the documentation
23//! [here](https://doc.rust-lang.org/stable/core/arch/) for more information.
24
25use crate::arity::*;
26use arrow_array::types::*;
27use arrow_array::*;
28use arrow_buffer::i256;
29use arrow_schema::*;
30use std::cmp::min;
31use std::sync::Arc;
32
33/// Returns the precision and scale of the result of a multiplication of two decimal types,
34/// and the divisor for fixed point multiplication.
35fn get_fixed_point_info(
36    left: (u8, i8),
37    right: (u8, i8),
38    required_scale: i8,
39) -> Result<(u8, i8, i256), ArrowError> {
40    let product_scale = left.1 + right.1;
41    let precision = min(left.0 + right.0 + 1, DECIMAL128_MAX_PRECISION);
42
43    if required_scale > product_scale {
44        return Err(ArrowError::ComputeError(format!(
45            "Required scale {required_scale} is greater than product scale {product_scale}",
46        )));
47    }
48
49    let divisor = i256::from_i128(10).pow_wrapping((product_scale - required_scale) as u32);
50
51    Ok((precision, product_scale, divisor))
52}
53
54/// Perform `left * right` operation on two decimal arrays. If either left or right value is
55/// null then the result is also null.
56///
57/// This performs decimal multiplication which allows precision loss if an exact representation
58/// is not possible for the result, according to the required scale. In the case, the result
59/// will be rounded to the required scale.
60///
61/// If the required scale is greater than the product scale, an error is returned.
62///
63/// This doesn't detect overflow. Once overflowing, the result will wrap around.
64///
65/// It is implemented for compatibility with precision loss `multiply` function provided by
66/// other data processing engines. For multiplication with precision loss detection, use
67/// `multiply_dyn` or `multiply_dyn_checked` instead.
68pub fn multiply_fixed_point_dyn(
69    left: &dyn Array,
70    right: &dyn Array,
71    required_scale: i8,
72) -> Result<ArrayRef, ArrowError> {
73    match (left.data_type(), right.data_type()) {
74        (DataType::Decimal128(_, _), DataType::Decimal128(_, _)) => {
75            let left = left.as_any().downcast_ref::<Decimal128Array>().unwrap();
76            let right = right.as_any().downcast_ref::<Decimal128Array>().unwrap();
77
78            multiply_fixed_point(left, right, required_scale).map(|a| Arc::new(a) as ArrayRef)
79        }
80        (_, _) => Err(ArrowError::CastError(format!(
81            "Unsupported data type {}, {}",
82            left.data_type(),
83            right.data_type()
84        ))),
85    }
86}
87
88/// Perform `left * right` operation on two decimal arrays. If either left or right value is
89/// null then the result is also null.
90///
91/// This performs decimal multiplication which allows precision loss if an exact representation
92/// is not possible for the result, according to the required scale. In the case, the result
93/// will be rounded to the required scale.
94///
95/// If the required scale is greater than the product scale, an error is returned.
96///
97/// It is implemented for compatibility with precision loss `multiply` function provided by
98/// other data processing engines. For multiplication with precision loss detection, use
99/// `multiply` or `multiply_checked` instead.
100pub fn multiply_fixed_point_checked(
101    left: &PrimitiveArray<Decimal128Type>,
102    right: &PrimitiveArray<Decimal128Type>,
103    required_scale: i8,
104) -> Result<PrimitiveArray<Decimal128Type>, ArrowError> {
105    let (precision, product_scale, divisor) = get_fixed_point_info(
106        (left.precision(), left.scale()),
107        (right.precision(), right.scale()),
108        required_scale,
109    )?;
110
111    if required_scale == product_scale {
112        return try_binary::<_, _, _, Decimal128Type>(left, right, |a, b| a.mul_checked(b))?
113            .with_precision_and_scale(precision, required_scale);
114    }
115
116    let round = divide_and_round(divisor);
117    let product = try_binary::<_, _, _, Decimal128Type>(left, right, |a, b| {
118        let a = i256::from_i128(a);
119        let b = i256::from_i128(b);
120
121        let mut mul = a.wrapping_mul(b);
122        mul = round(mul);
123        mul.to_i128().ok_or_else(|| {
124            ArrowError::ArithmeticOverflow(format!("Overflow happened on: {a:?} * {b:?}"))
125        })
126    })?;
127    product.with_precision_and_scale(precision, required_scale)
128}
129
130/// Perform `left * right` operation on two decimal arrays. If either left or right value is
131/// null then the result is also null.
132///
133/// This performs decimal multiplication which allows precision loss if an exact representation
134/// is not possible for the result, according to the required scale. In the case, the result
135/// will be rounded to the required scale.
136///
137/// If the required scale is greater than the product scale, an error is returned.
138///
139/// This doesn't detect overflow. Once overflowing, the result will wrap around.
140/// For an overflow-checking variant, use `multiply_fixed_point_checked` instead.
141///
142/// It is implemented for compatibility with precision loss `multiply` function provided by
143/// other data processing engines. For multiplication with precision loss detection, use
144/// `multiply` or `multiply_checked` instead.
145pub fn multiply_fixed_point(
146    left: &PrimitiveArray<Decimal128Type>,
147    right: &PrimitiveArray<Decimal128Type>,
148    required_scale: i8,
149) -> Result<PrimitiveArray<Decimal128Type>, ArrowError> {
150    let (precision, product_scale, divisor) = get_fixed_point_info(
151        (left.precision(), left.scale()),
152        (right.precision(), right.scale()),
153        required_scale,
154    )?;
155
156    if required_scale == product_scale {
157        return binary(left, right, |a, b| a.mul_wrapping(b))?
158            .with_precision_and_scale(precision, required_scale);
159    }
160
161    let round = divide_and_round(divisor);
162    let product = binary::<_, _, _, Decimal128Type>(left, right, |a, b| {
163        let a = i256::from_i128(a);
164        let b = i256::from_i128(b);
165
166        let mut mul = a.wrapping_mul(b);
167        mul = round(mul);
168        mul.as_i128()
169    })?;
170    product.with_precision_and_scale(precision, required_scale)
171}
172
173/// Returns a function that divides a value by `div` and rounds the result half away from zero
174fn divide_and_round(div: i256) -> impl Fn(i256) -> i256 {
175    let half = div.div_wrapping(i256::from_i128(2));
176    let half_neg = half.neg_wrapping();
177
178    move |input| {
179        let (d, r) = input.wrapping_div_rem(div);
180
181        // Round result
182        match input >= i256::ZERO {
183            true if r >= half => d.add_wrapping(i256::ONE),
184            false if r <= half_neg => d.sub_wrapping(i256::ONE),
185            _ => d,
186        }
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193    use crate::numeric::mul;
194
195    #[test]
196    fn test_decimal_multiply_allow_precision_loss() {
197        // Overflow happening as i128 cannot hold multiplying result.
198        // [123456789]
199        let a = Decimal128Array::from(vec![123456789000000000000000000])
200            .with_precision_and_scale(38, 18)
201            .unwrap();
202
203        // [10]
204        let b = Decimal128Array::from(vec![10000000000000000000])
205            .with_precision_and_scale(38, 18)
206            .unwrap();
207
208        let err = mul(&a, &b).unwrap_err();
209        assert!(
210            err.to_string().contains(
211                "Overflow happened on: 123456789000000000000000000 * 10000000000000000000"
212            )
213        );
214
215        // Allow precision loss.
216        let result = multiply_fixed_point_checked(&a, &b, 28).unwrap();
217        // [1234567890]
218        let expected = Decimal128Array::from(vec![12345678900000000000000000000000000000])
219            .with_precision_and_scale(38, 28)
220            .unwrap();
221
222        assert_eq!(&expected, &result);
223        assert_eq!(
224            result.value_as_string(0),
225            "1234567890.0000000000000000000000000000"
226        );
227
228        // Rounding case
229        // [0.000000000000000001, 123456789.555555555555555555, 1.555555555555555555]
230        let a = Decimal128Array::from(vec![1, 123456789555555555555555555, 1555555555555555555])
231            .with_precision_and_scale(38, 18)
232            .unwrap();
233
234        // [1.555555555555555555, 11.222222222222222222, 0.000000000000000001]
235        let b = Decimal128Array::from(vec![1555555555555555555, 11222222222222222222, 1])
236            .with_precision_and_scale(38, 18)
237            .unwrap();
238
239        let result = multiply_fixed_point_checked(&a, &b, 28).unwrap();
240        // [
241        //    0.0000000000000000015555555556,
242        //    1385459527.2345679012071330528765432099,
243        //    0.0000000000000000015555555556
244        // ]
245        let expected = Decimal128Array::from(vec![
246            15555555556,
247            13854595272345679012071330528765432099,
248            15555555556,
249        ])
250        .with_precision_and_scale(38, 28)
251        .unwrap();
252
253        assert_eq!(&expected, &result);
254
255        // Rounded the value "1385459527.234567901207133052876543209876543210".
256        assert_eq!(
257            result.value_as_string(1),
258            "1385459527.2345679012071330528765432099"
259        );
260        assert_eq!(result.value_as_string(0), "0.0000000000000000015555555556");
261        assert_eq!(result.value_as_string(2), "0.0000000000000000015555555556");
262
263        let a = Decimal128Array::from(vec![1230])
264            .with_precision_and_scale(4, 2)
265            .unwrap();
266
267        let b = Decimal128Array::from(vec![1000])
268            .with_precision_and_scale(4, 2)
269            .unwrap();
270
271        // Required scale is same as the product of the input scales. Behavior is same as multiply.
272        let result = multiply_fixed_point_checked(&a, &b, 4).unwrap();
273        assert_eq!(result.precision(), 9);
274        assert_eq!(result.scale(), 4);
275
276        let expected = mul(&a, &b).unwrap();
277        assert_eq!(expected.as_ref(), &result);
278
279        // Required scale cannot be larger than the product of the input scales.
280        let result = multiply_fixed_point_checked(&a, &b, 5).unwrap_err();
281        assert!(
282            result
283                .to_string()
284                .contains("Required scale 5 is greater than product scale 4")
285        );
286    }
287
288    #[test]
289    fn test_decimal_multiply_allow_precision_loss_overflow() {
290        // [99999999999123456789]
291        let a = Decimal128Array::from(vec![99999999999123456789000000000000000000])
292            .with_precision_and_scale(38, 18)
293            .unwrap();
294
295        // [9999999999910]
296        let b = Decimal128Array::from(vec![9999999999910000000000000000000])
297            .with_precision_and_scale(38, 18)
298            .unwrap();
299
300        let err = multiply_fixed_point_checked(&a, &b, 28).unwrap_err();
301        assert!(err.to_string().contains(
302            "Overflow happened on: 99999999999123456789000000000000000000 * 9999999999910000000000000000000"
303        ));
304
305        let result = multiply_fixed_point(&a, &b, 28).unwrap();
306        let expected = Decimal128Array::from(vec![62946009661555981610246871926660136960])
307            .with_precision_and_scale(38, 28)
308            .unwrap();
309
310        assert_eq!(&expected, &result);
311    }
312
313    #[test]
314    fn test_decimal_multiply_fixed_point() {
315        // [123456789]
316        let a = Decimal128Array::from(vec![123456789000000000000000000])
317            .with_precision_and_scale(38, 18)
318            .unwrap();
319
320        // [10]
321        let b = Decimal128Array::from(vec![10000000000000000000])
322            .with_precision_and_scale(38, 18)
323            .unwrap();
324
325        // `multiply` overflows on this case.
326        let err = mul(&a, &b).unwrap_err();
327        assert_eq!(
328            err.to_string(),
329            "Arithmetic overflow: Overflow happened on: 123456789000000000000000000 * 10000000000000000000"
330        );
331
332        // Avoid overflow by reducing the scale.
333        let result = multiply_fixed_point(&a, &b, 28).unwrap();
334        // [1234567890]
335        let expected = Decimal128Array::from(vec![12345678900000000000000000000000000000])
336            .with_precision_and_scale(38, 28)
337            .unwrap();
338
339        assert_eq!(&expected, &result);
340        assert_eq!(
341            result.value_as_string(0),
342            "1234567890.0000000000000000000000000000"
343        );
344    }
345
346    #[test]
347    fn test_decimal_multiply_fixed_point_rounding() {
348        // Products below, at and above half for each sign, rounded from scale 1 to scale 0
349        let a = Decimal128Array::from(vec![
350            Some(14),
351            Some(15),
352            Some(16),
353            Some(-14),
354            Some(-15),
355            Some(-16),
356            Some(25),
357            Some(-25),
358            None,
359        ])
360        .with_precision_and_scale(38, 1)
361        .unwrap();
362        let b = Decimal128Array::from(vec![1, 1, 1, 1, 1, 1, -1, -1, 1])
363            .with_precision_and_scale(38, 0)
364            .unwrap();
365        let expected = Decimal128Array::from(vec![
366            Some(1),
367            Some(2),
368            Some(2),
369            Some(-1),
370            Some(-2),
371            Some(-2),
372            Some(-3),
373            Some(3),
374            None,
375        ])
376        .with_precision_and_scale(38, 0)
377        .unwrap();
378
379        // The slice without the null covers the paths without nulls
380        for (a, b, expected) in [
381            (a.clone(), b.clone(), expected.clone()),
382            (a.slice(0, 8), b.slice(0, 8), expected.slice(0, 8)),
383        ] {
384            assert_eq!(multiply_fixed_point(&a, &b, 0).unwrap(), expected);
385            assert_eq!(multiply_fixed_point_checked(&a, &b, 0).unwrap(), expected);
386        }
387    }
388}