Skip to content
Merged
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
98 changes: 98 additions & 0 deletions src/kernels/arithmetic/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,104 @@ mod tests {
}
}

#[cfg(feature = "scalar_type")]
#[test]
fn scalar_operands_apply_the_requested_operator() {
use crate::Scalar;
use crate::enums::operators::ArithmeticOperator;
use crate::kernels::broadcast::broadcast_value;

let apply = |op| {
broadcast_value(
op,
Value::Scalar(Scalar::Int32(7)),
Value::Scalar(Scalar::Int32(2)),
)
.unwrap()
};

let Value::Scalar(Scalar::Int32(diff)) = apply(ArithmeticOperator::Subtract) else {
panic!("Expected Int32 scalar from subtraction");
};
assert_eq!(diff, 5);

let Value::Scalar(Scalar::Int32(prod)) = apply(ArithmeticOperator::Multiply) else {
panic!("Expected Int32 scalar from multiplication");
};
assert_eq!(prod, 14);

// Integer scalar division is true division with a Float64 result.
let Value::Scalar(Scalar::Float64(quot)) = apply(ArithmeticOperator::Divide) else {
panic!("Expected Float64 scalar from division");
};
assert_eq!(quot, 3.5);

let Value::Scalar(Scalar::Int32(rem)) = apply(ArithmeticOperator::Remainder) else {
panic!("Expected Int32 scalar from remainder");
};
assert_eq!(rem, 1);

let Value::Scalar(Scalar::Int32(pow)) = apply(ArithmeticOperator::Power) else {
panic!("Expected Int32 scalar from power");
};
assert_eq!(pow, 49);

let Value::Scalar(Scalar::Int32(floor)) = apply(ArithmeticOperator::FloorDiv) else {
panic!("Expected Int32 scalar from floor division");
};
assert_eq!(floor, 3);

// Remainder keeps the dividend's sign and floor division rounds
// towards negative infinity.
let negative = |op| {
broadcast_value(
op,
Value::Scalar(Scalar::Int32(-7)),
Value::Scalar(Scalar::Int32(2)),
)
.unwrap()
};
let Value::Scalar(Scalar::Int32(rem)) = negative(ArithmeticOperator::Remainder) else {
panic!("Expected Int32 scalar from remainder");
};
assert_eq!(rem, -1);
let Value::Scalar(Scalar::Int32(floor)) = negative(ArithmeticOperator::FloorDiv) else {
panic!("Expected Int32 scalar from floor division");
};
assert_eq!(floor, -4);

let float = |op| {
broadcast_value(
op,
Value::Scalar(Scalar::Float64(7.5)),
Value::Scalar(Scalar::Float64(2.0)),
)
.unwrap()
};
let Value::Scalar(Scalar::Float64(rem)) = float(ArithmeticOperator::Remainder) else {
panic!("Expected Float64 scalar from remainder");
};
assert_eq!(rem, 1.5);
let Value::Scalar(Scalar::Float64(pow)) = float(ArithmeticOperator::Power) else {
panic!("Expected Float64 scalar from power");
};
assert!((pow - 56.25).abs() < 1e-9);
let Value::Scalar(Scalar::Float64(floor)) = float(ArithmeticOperator::FloorDiv) else {
panic!("Expected Float64 scalar from floor division");
};
assert_eq!(floor, 3.0);

// A zero divisor and a negative integer exponent return errors.
let zero = Value::Scalar(Scalar::Int32(0));
let seven = Value::Scalar(Scalar::Int32(7));
let negative_one = Value::Scalar(Scalar::Int32(-1));
assert!(
broadcast_value(ArithmeticOperator::Remainder, seven.clone(), zero.clone()).is_err()
);
assert!(broadcast_value(ArithmeticOperator::FloorDiv, seven.clone(), zero).is_err());
assert!(broadcast_value(ArithmeticOperator::Power, seven, negative_one).is_err());
}

#[test]
fn test_reference_operations() {
let arr1 =
Expand Down
2 changes: 1 addition & 1 deletion src/kernels/broadcast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ pub fn broadcast_value(
// Scalar + Scalar = Scalar
#[cfg(feature = "scalar_type")]
(Value::Scalar(l), Value::Scalar(r)) => {
scalar_arithmetic(l, r, ArithmeticOperator::Add).map(Value::Scalar)
scalar_arithmetic(l, r, op).map(Value::Scalar)
}

// Array types - use resolve_binary_arithmetic
Expand Down
71 changes: 71 additions & 0 deletions src/kernels/routing/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ use crate::kernels::arithmetic::{
},
string_ops::apply_str_str,
};
#[cfg(feature = "scalar_type")]
use crate::kernels::arithmetic::std::int_dense_body_std;
#[cfg(feature = "scalar_type")]
use num_traits::ToPrimitive;
#[cfg(feature = "decimal")]
use crate::kernels::arithmetic::decimal::{decimal_binary, integer_to_decimal, max_precision};

Expand All @@ -43,6 +47,38 @@ pub fn scalar_arithmetic(
use ArithmeticOperator::*;
use Scalar;

// Computes `Remainder`, `Power` or `FloorDiv` for two integer scalars with the
// integer array kernel `int_dense_body_std`, applied to one-element slices.
//
// ## Behaviour
// - Remainder keeps the dividend's sign.
// - Floor division rounds towards negative infinity and keeps the integer type.
// - Power uses wrapping exponentiation by squaring.
// - The kernel panics on a zero divisor, an overflowing division and an
// exponent outside `u32`. These inputs are checked first and return
// `MinarrowError::KernelError`.
macro_rules! integer_scalar_op {
($l:expr, $r:expr, $op:expr) => {{
let (l, r, op) = ($l, $r, $op);
if op == Power {
if r.to_u32().is_none() {
return Err(MinarrowError::KernelError(Some(format!(
"Power exponent {} is outside the u32 range. Consider casting to float first.",
r
))));
}
} else if l.checked_rem(r).is_none() {
return Err(MinarrowError::KernelError(Some(format!(
"Integer {:?} of {} by {} has a zero divisor or overflows",
op, l, r
))));
}
let mut out = [l];
int_dense_body_std(op, &[l], &[r], &mut out);
out[0]
}};
}

let result = match (lhs, rhs, op) {
// Int32 operations
(Scalar::Int32(l), Scalar::Int32(r), Add) => Scalar::Int32(l + r),
Expand All @@ -51,24 +87,37 @@ pub fn scalar_arithmetic(
// Integer scalar division is true division and returns a Float64
// scalar, so 7 / 2 is 3.5 and division by zero follows IEEE 754.
(Scalar::Int32(l), Scalar::Int32(r), Divide) => Scalar::Float64(l as f64 / r as f64),
(Scalar::Int32(l), Scalar::Int32(r), op @ (Remainder | Power | FloorDiv)) => {
Scalar::Int32(integer_scalar_op!(l, r, op))
}

// Int64 operations
(Scalar::Int64(l), Scalar::Int64(r), Add) => Scalar::Int64(l + r),
(Scalar::Int64(l), Scalar::Int64(r), Subtract) => Scalar::Int64(l - r),
(Scalar::Int64(l), Scalar::Int64(r), Multiply) => Scalar::Int64(l * r),
(Scalar::Int64(l), Scalar::Int64(r), Divide) => Scalar::Float64(l as f64 / r as f64),
(Scalar::Int64(l), Scalar::Int64(r), op @ (Remainder | Power | FloorDiv)) => {
Scalar::Int64(integer_scalar_op!(l, r, op))
}

// Float32 operations
(Scalar::Float32(l), Scalar::Float32(r), Add) => Scalar::Float32(l + r),
(Scalar::Float32(l), Scalar::Float32(r), Subtract) => Scalar::Float32(l - r),
(Scalar::Float32(l), Scalar::Float32(r), Multiply) => Scalar::Float32(l * r),
(Scalar::Float32(l), Scalar::Float32(r), Divide) => Scalar::Float32(l / r),
(Scalar::Float32(l), Scalar::Float32(r), Remainder) => Scalar::Float32(l % r),
// Float power uses logarithmic exponentiation, matching the float array kernels.
(Scalar::Float32(l), Scalar::Float32(r), Power) => Scalar::Float32((r * l.ln()).exp()),
(Scalar::Float32(l), Scalar::Float32(r), FloorDiv) => Scalar::Float32((l / r).floor()),

// Float64 operations
(Scalar::Float64(l), Scalar::Float64(r), Add) => Scalar::Float64(l + r),
(Scalar::Float64(l), Scalar::Float64(r), Subtract) => Scalar::Float64(l - r),
(Scalar::Float64(l), Scalar::Float64(r), Multiply) => Scalar::Float64(l * r),
(Scalar::Float64(l), Scalar::Float64(r), Divide) => Scalar::Float64(l / r),
(Scalar::Float64(l), Scalar::Float64(r), Remainder) => Scalar::Float64(l % r),
(Scalar::Float64(l), Scalar::Float64(r), Power) => Scalar::Float64((r * l.ln()).exp()),
(Scalar::Float64(l), Scalar::Float64(r), FloorDiv) => Scalar::Float64((l / r).floor()),

// Mixed type promotion (Int + Float = Float)
(Scalar::Int32(l), Scalar::Float32(r), op) => {
Expand All @@ -93,6 +142,10 @@ pub fn scalar_arithmetic(
(Scalar::Int8(l), Scalar::Int8(r), Multiply) => Scalar::Int8(l * r),
#[cfg(feature = "extended_numeric_types")]
(Scalar::Int8(l), Scalar::Int8(r), Divide) => Scalar::Float64(l as f64 / r as f64),
#[cfg(feature = "extended_numeric_types")]
(Scalar::Int8(l), Scalar::Int8(r), op @ (Remainder | Power | FloorDiv)) => {
Scalar::Int8(integer_scalar_op!(l, r, op))
}

// Int16
#[cfg(feature = "extended_numeric_types")]
Expand All @@ -103,6 +156,10 @@ pub fn scalar_arithmetic(
(Scalar::Int16(l), Scalar::Int16(r), Multiply) => Scalar::Int16(l * r),
#[cfg(feature = "extended_numeric_types")]
(Scalar::Int16(l), Scalar::Int16(r), Divide) => Scalar::Float64(l as f64 / r as f64),
#[cfg(feature = "extended_numeric_types")]
(Scalar::Int16(l), Scalar::Int16(r), op @ (Remainder | Power | FloorDiv)) => {
Scalar::Int16(integer_scalar_op!(l, r, op))
}

// UInt8
#[cfg(feature = "extended_numeric_types")]
Expand All @@ -113,6 +170,10 @@ pub fn scalar_arithmetic(
(Scalar::UInt8(l), Scalar::UInt8(r), Multiply) => Scalar::UInt8(l * r),
#[cfg(feature = "extended_numeric_types")]
(Scalar::UInt8(l), Scalar::UInt8(r), Divide) => Scalar::Float64(l as f64 / r as f64),
#[cfg(feature = "extended_numeric_types")]
(Scalar::UInt8(l), Scalar::UInt8(r), op @ (Remainder | Power | FloorDiv)) => {
Scalar::UInt8(integer_scalar_op!(l, r, op))
}

// UInt16
#[cfg(feature = "extended_numeric_types")]
Expand All @@ -123,18 +184,28 @@ pub fn scalar_arithmetic(
(Scalar::UInt16(l), Scalar::UInt16(r), Multiply) => Scalar::UInt16(l * r),
#[cfg(feature = "extended_numeric_types")]
(Scalar::UInt16(l), Scalar::UInt16(r), Divide) => Scalar::Float64(l as f64 / r as f64),
#[cfg(feature = "extended_numeric_types")]
(Scalar::UInt16(l), Scalar::UInt16(r), op @ (Remainder | Power | FloorDiv)) => {
Scalar::UInt16(integer_scalar_op!(l, r, op))
}

// UInt32
(Scalar::UInt32(l), Scalar::UInt32(r), Add) => Scalar::UInt32(l + r),
(Scalar::UInt32(l), Scalar::UInt32(r), Subtract) => Scalar::UInt32(l - r),
(Scalar::UInt32(l), Scalar::UInt32(r), Multiply) => Scalar::UInt32(l * r),
(Scalar::UInt32(l), Scalar::UInt32(r), Divide) => Scalar::Float64(l as f64 / r as f64),
(Scalar::UInt32(l), Scalar::UInt32(r), op @ (Remainder | Power | FloorDiv)) => {
Scalar::UInt32(integer_scalar_op!(l, r, op))
}

// UInt64
(Scalar::UInt64(l), Scalar::UInt64(r), Add) => Scalar::UInt64(l + r),
(Scalar::UInt64(l), Scalar::UInt64(r), Subtract) => Scalar::UInt64(l - r),
(Scalar::UInt64(l), Scalar::UInt64(r), Multiply) => Scalar::UInt64(l * r),
(Scalar::UInt64(l), Scalar::UInt64(r), Divide) => Scalar::Float64(l as f64 / r as f64),
(Scalar::UInt64(l), Scalar::UInt64(r), op @ (Remainder | Power | FloorDiv)) => {
Scalar::UInt64(integer_scalar_op!(l, r, op))
}
// String concatenation
(Scalar::String32(l), Scalar::String32(r), Add) => Scalar::String32(format!("{}{}", l, r)),
#[cfg(feature = "large_string")]
Expand Down
Loading