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
27 changes: 21 additions & 6 deletions src/enums/operators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,33 @@ pub enum ArithmeticOperator {
Multiply,
/// Division (`lhs / rhs`)
///
/// For integers, division by zero panics in unmasked arrays and nullifies in masked arrays.
/// For floating-point, follows IEEE 754 (yields ±Inf or NaN).
/// Division is true division: `7 / 2` is `3.5`. The `Scalar` arms
/// return a `Float64` scalar for integer operands, and callers dividing
/// integer arrays cast their operands to `f64` before dispatch so the
/// float kernels produce the float result.
///
/// The integer slice kernels themselves serve `FloorDiv`, so `Divide`
/// handed raw integer slices at the kernel level behaves as floor
/// division. Cast to float first for a true-division result.
///
/// Division by zero on floats follows IEEE 754: a nonzero value over
/// zero yields Inf with the operands' sign, and zero over zero yields
/// NaN. On raw integer slices it panics in unmasked arrays and
/// nullifies in masked arrays.
Divide,
/// Modulus/remainder operation (`lhs % rhs`)
///
/// Behaviour matches Rust's `%` operator. Division by zero handling follows same
/// rules as `Divide` operation.
/// Behaviour matches Rust's `%` operator: the result keeps the
/// dividend's sign, so `-7 % 2` is `-1`. Division by zero handling
/// follows same rules as `Divide` operation.
Remainder,
/// Exponentiation (`lhs ^ rhs`)
///
/// For integers, uses repeated multiplication. For floating-point, uses `pow()` function.
/// Negative exponents on integers may yield zero due to truncation.
/// For integers, exponentiation by squaring with wrapping
/// multiplication, so overflow wraps like the other integer arms. The
/// exponent must convert to `u32`: a negative or larger exponent
/// returns an error advising a cast to float. For floating-point, uses
/// logarithmic computation.
Power,
/// Floor division (`lhs // rhs`)
///
Expand Down
60 changes: 59 additions & 1 deletion src/enums/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ impl Value {
Value::Array(a) => a.len(),

#[cfg(feature = "views")]
Value::ArrayView(av) => av.array.len(),
Value::ArrayView(av) => av.len(),

Value::FieldArray(fa) => fa.array.len(),

Expand Down Expand Up @@ -376,3 +376,61 @@ impl Value {
}

// Also see typed accessors in ./conversions.rs and trait impls in ./impls.rs

#[cfg(test)]
mod tests {
use std::sync::Arc;

use super::*;
use crate::{
Array, ArrayV, ArrowType, Field, FieldArray, IntegerArray, MaskedArray, NumericArray, Table,
};

fn seq_array(n: usize) -> Array {
let mut arr = IntegerArray::<i64>::default();
for i in 0..n {
arr.push(i as i64);
}
Array::NumericArray(NumericArray::Int64(Arc::new(arr)))
}

/// `len` counts the rows a view covers, so callers can pass the count
/// straight to `slice`.
#[cfg(feature = "views")]
#[test]
fn len_of_an_array_view_counts_the_window() {
let view = ArrayV::new(seq_array(100), 30, 10);
let value = Value::ArrayView(Arc::new(view));

assert_eq!(value.len(), 10, "the window, not the backing array");
}

/// The two view variants agree, so an operator sizing its work from
/// `len` behaves the same whichever shape it was handed.
#[cfg(feature = "views")]
#[test]
fn len_agrees_across_the_view_variants() {
let array = Value::ArrayView(Arc::new(ArrayV::new(seq_array(100), 30, 10)));

let mut table = Table::new("t".to_string(), None);
table.add_col(FieldArray::new(
Field::new("v", ArrowType::Int64, false, None),
seq_array(100),
));
let table = Value::Table(Arc::new(table)).slice(30, 10);

assert_eq!(array.len(), table.len());
}

/// `len` bounds a `slice` on the same value. The two are read together
/// wherever work is split into row ranges, so a `len` drawn from the
/// backing array would run a window past its own end.
#[cfg(feature = "views")]
#[test]
fn len_bounds_a_slice_of_the_same_value() {
let value = Value::ArrayView(Arc::new(ArrayV::new(seq_array(100), 30, 10)));

let whole = value.slice(0, value.len());
assert_eq!(whole.len(), 10);
}
}
75 changes: 75 additions & 0 deletions src/kernels/arithmetic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,81 @@ mod tests {
apply_int_u64
);

// At the slice-kernel level, integer division rounds towards negative
// infinity. Rust's `/` truncates towards zero, so the two differ on
// every negative inexact quotient: -7 / 2 floors to -4 where truncation
// gives -3. Divide and FloorDiv therefore agree on integer slices.
// Remainder keeps the dividend's sign per Rust's `%`.

#[test]
fn int_divide_floors_towards_negative_infinity_dense() {
let lhs = vec64![-7i64, 7, -7, 8, -8];
let rhs = vec64![2i64, 2, -2, 2, 2];

let out = apply_int_i64(&lhs, &rhs, ArithmeticOperator::Divide, None).unwrap();
assert_int(&out, &[-4, 3, 3, 4, -4], None);

let floor = apply_int_i64(&lhs, &rhs, ArithmeticOperator::FloorDiv, None).unwrap();
assert_eq!(
out.data.as_slice(),
floor.data.as_slice(),
"Divide and FloorDiv agree on integers"
);
}

#[test]
fn int_divide_floors_towards_negative_infinity_masked() {
let lhs = vec64![-7i64, 8, 9];
let rhs = vec64![2i64, 0, 3];
let mask = bitmask(&[true, true, true]);

let out = apply_int_i64(&lhs, &rhs, ArithmeticOperator::Divide, Some(&mask)).unwrap();
assert_int(&out, &[-4, 0, 3], Some(&[true, false, true]));
}

#[test]
fn int_divide_unsigned_never_rounds() {
// Unsigned quotients never round below zero, so the floor rule
// leaves them identical to truncation.
let lhs = vec64![7u64, 9];
let rhs = vec64![2u64, 4];

let out = apply_int_u64(&lhs, &rhs, ArithmeticOperator::Divide, None).unwrap();
assert_int(&out, &[3, 2], None);
}

#[test]
fn int_remainder_keeps_dividend_sign() {
let lhs = vec64![-7i64, 7, -7];
let rhs = vec64![2i64, -2, -2];

let out = apply_int_i64(&lhs, &rhs, ArithmeticOperator::Remainder, None).unwrap();
assert_int(&out, &[-1, 1, -1], None);
}

#[cfg(feature = "scalar_type")]
#[test]
fn scalar_divide_is_true_division() {
use crate::Scalar;
use crate::kernels::routing::arithmetic::scalar_arithmetic;

let out = scalar_arithmetic(
Scalar::Int64(-7),
Scalar::Int64(2),
ArithmeticOperator::Divide,
)
.unwrap();
assert_eq!(out, Scalar::Float64(-3.5));

let out = scalar_arithmetic(
Scalar::UInt32(7),
Scalar::UInt32(2),
ArithmeticOperator::Divide,
)
.unwrap();
assert_eq!(out, Scalar::Float64(3.5));
}

macro_rules! float_kernel_suite {
($test_fn:ident, $ty:ty, $apply_fn:ident, $eps:expr) => {
#[test]
Expand Down
Loading
Loading