Skip to main content

radiate_utils/datatype/
arithmetic.rs

1use radiate_error::radiate_bail;
2
3use crate::AnyValue;
4use std::ops::{Add, Div, Mul, Rem, Sub};
5use std::ops::{BitAnd, BitOr, Not};
6
7/// Internal helper: perform `lhs <op> rhs` for all numeric AnyValue variants.
8/// On type mismatch, returns `AnyValue::Null`.
9macro_rules! bin_numeric_op {
10    ($lhs:expr, $rhs:expr, $op:tt) => {{
11        use AnyValue::*;
12        match ($lhs, $rhs) {
13            (Int8(a),    Int8(b))    => Int8(a $op b),
14            (Int8(a),   Int16(b))   => Int16(i16::from(a) $op b),
15            (Int8(a),   Int32(b))   => Int32(i32::from(a) $op b),
16            (Int8(a),   Int64(b))   => Int64(i64::from(a) $op b),
17            (Int8(a),   Int128(b))  => Int128(i128::from(a) $op b),
18
19            (Int16(a),   Int8(b))   => Int16(i16::from(a) $op i16::from(b)),
20            (Int16(a),   Int16(b))   => Int16(a $op b),
21            (Int16(a),   Int32(b))   => Int32(i32::from(a) $op b),
22            (Int16(a),   Int64(b))   => Int64(i64::from(a) $op b),
23            (Int16(a),   Int128(b))  => Int128(i128::from(a) $op b),
24
25            (Int32(a),   Int8(b))   => Int32(a $op i32::from(b)),
26            (Int32(a),   Int16(b))   => Int32(a $op i32::from(b)),
27            (Int32(a),   Int32(b))   => Int32(a $op b),
28            (Int32(a),   Int64(b))   => Int64(i64::from(a) $op b),
29            (Int32(a),   Int128(b))  => Int128(i128::from(a) $op b),
30
31            (Int64(a),   Int8(b))   => Int64(a $op i64::from(b)),
32            (Int64(a),   Int16(b))   => Int64(a $op i64::from(b)),
33            (Int64(a),   Int32(b))   => Int64(a $op i64::from(b)),
34            (Int64(a),   Int64(b))   => Int64(a $op b),
35            (Int64(a),   Int128(b))  => Int128(i128::from(a) $op b),
36
37            (Int128(a),   Int8(b))   => Int128(a $op i128::from(b)),
38            (Int128(a),   Int16(b))   => Int128(a $op i128::from(b)),
39            (Int128(a),   Int32(b))   => Int128(a $op i128::from(b)),
40            (Int128(a),   Int64(b))   => Int128(a $op i128::from(b)),
41            (Int128(a),  Int128(b))  => Int128(a $op b),
42
43            (UInt8(a),   UInt8(b))   => UInt8(a $op b),
44            (UInt8(a),  UInt16(b))  => UInt16(u16::from(a) $op b),
45            (UInt8(a),  UInt32(b))  => UInt32(u32::from(a) $op b),
46            (UInt8(a),  UInt64(b))  => UInt64(u64::from(a) $op b),
47            (UInt8(a),  UInt128(b)) => UInt128(u128::from(a) $op b),
48
49            (UInt16(a),  UInt8(b))  => UInt16(a $op u16::from(b)),
50            (UInt16(a),  UInt16(b))  => UInt16(a $op b),
51            (UInt16(a),  UInt32(b))  => UInt32(u32::from(a) $op b),
52            (UInt16(a),  UInt64(b))  => UInt64(u64::from(a) $op b),
53            (UInt16(a),  UInt128(b)) => UInt128(u128::from(a) $op b),
54
55            (UInt32(a),  UInt8(b))  => UInt32(a $op u32::from(b)),
56            (UInt32(a),  UInt16(b))  => UInt32(a $op u32::from(b)),
57            (UInt32(a),  UInt32(b))  => UInt32(a $op b),
58            (UInt32(a),  UInt64(b))  => UInt64(u64::from(a) $op b),
59            (UInt32(a),  UInt128(b)) => UInt128(u128::from(a) $op b),
60
61            (UInt64(a),  UInt8(b))  => UInt64(a $op u64::from(b)),
62            (UInt64(a),  UInt16(b))  => UInt64(a $op u64::from(b)),
63            (UInt64(a),  UInt32(b))  => UInt64(a $op u64::from(b)),
64            (UInt64(a),  UInt64(b))  => UInt64(a $op b),
65            (UInt64(a),  UInt128(b)) => UInt128(u128::from(a) $op b),
66
67            (Float32(a), Float32(b)) => Float32(a $op b),
68            (Float64(a), Float32(b)) => Float64(a $op b as f64),
69
70            (Float64(a), Float64(b)) => Float64(a $op b),
71            (Float32(a), Float64(b)) => Float64(a as f64 $op b),
72            _ => Null,
73        }
74    }};
75}
76
77macro_rules! bin_numeric_div {
78    ($lhs:expr, $rhs:expr) => {{
79        use AnyValue::*;
80        match ($lhs, $rhs) {
81            (Int8(a), Int8(b)) => Int8(if b == 0 { a } else { a / b }),
82            (Int16(a), Int8(b)) => Int16(if b == 0 { a } else { a / i16::from(b) }),
83            (Int32(a), Int8(b)) => Int32(if b == 0 { a } else { a / i32::from(b) }),
84            (Int64(a), Int8(b)) => Int64(if b == 0 { a } else { a / i64::from(b) }),
85            (Int128(a), Int8(b)) => Int128(if b == 0 { a } else { a / i128::from(b) }),
86
87            (Int16(a), Int16(b)) => Int16(if b == 0 { a } else { a / b }),
88            (Int32(a), Int16(b)) => Int32(if b == 0 { a } else { a / i32::from(b) }),
89            (Int64(a), Int16(b)) => Int64(if b == 0 { a } else { a / i64::from(b) }),
90            (Int128(a), Int16(b)) => Int128(if b == 0 { a } else { a / i128::from(b) }),
91
92            (Int32(a), Int32(b)) => Int32(if b == 0 { a } else { a / b }),
93            (Int64(a), Int32(b)) => Int64(if b == 0 { a } else { a / i64::from(b) }),
94            (Int128(a), Int32(b)) => Int128(if b == 0 { a } else { a / i128::from(b) }),
95
96            (Int64(a), Int64(b)) => Int64(if b == 0 { a } else { a / b }),
97            (Int128(a), Int64(b)) => Int128(if b == 0 { a } else { a / i128::from(b) }),
98
99            (Int128(a), Int128(b)) => Int128(if b == 0 { a } else { a / b }),
100
101            (UInt8(a), UInt8(b)) => UInt8(if b == 0 { a } else { a / b }),
102            (UInt8(a), UInt16(b)) => UInt16(if b == 0 { a as u16 } else { (a as u16) / b }),
103            (UInt8(a), UInt32(b)) => UInt32(if b == 0 { a as u32 } else { (a as u32) / b }),
104            (UInt8(a), UInt64(b)) => UInt64(if b == 0 { a as u64 } else { (a as u64) / b }),
105            (UInt8(a), UInt128(b)) => UInt128(if b == 0 { a as u128 } else { (a as u128) / b }),
106
107            (UInt16(a), UInt16(b)) => UInt16(if b == 0 { a } else { a / b }),
108            (UInt16(a), UInt32(b)) => UInt32(if b == 0 { a as u32 } else { (a as u32) / b }),
109            (UInt16(a), UInt64(b)) => UInt64(if b == 0 { a as u64 } else { (a as u64) / b }),
110            (UInt16(a), UInt128(b)) => UInt128(if b == 0 { a as u128 } else { (a as u128) / b }),
111
112            (UInt32(a), UInt32(b)) => UInt32(if b == 0 { a } else { a / b }),
113            (UInt32(a), UInt64(b)) => UInt64(if b == 0 { a as u64 } else { (a as u64) / b }),
114            (UInt32(a), UInt128(b)) => UInt128(if b == 0 { a as u128 } else { (a as u128) / b }),
115
116            (UInt64(a), UInt64(b)) => UInt64(if b == 0 { a } else { a / b }),
117            (UInt64(a), UInt128(b)) => UInt128(if b == 0 { a as u128 } else { (a as u128) / b }),
118
119            (Float32(a), Float32(b)) => {
120                if b == 0.0 {
121                    Null
122                } else {
123                    Float32(a / b)
124                }
125            }
126            (Float64(a), Float64(b)) => {
127                if b == 0.0 {
128                    Null
129                } else {
130                    Float64(a / b)
131                }
132            }
133            (Float32(a), Float64(b)) => {
134                if b == 0.0 {
135                    Null
136                } else {
137                    Float64((a as f64) / b)
138                }
139            }
140            (Float64(a), Float32(b)) => {
141                if b == 0.0 {
142                    Null
143                } else {
144                    Float64(a / (b as f64))
145                }
146            }
147            _ => panic!("Division is only supported for numeric types"),
148        }
149    }};
150}
151
152impl Add for AnyValue<'_> {
153    type Output = Self;
154
155    #[inline(always)]
156    fn add(self, other: Self) -> Self {
157        use AnyValue::*;
158        let is_numeric = self.dtype().is_numeric() && other.dtype().is_numeric();
159        let is_nested = self.is_nested() && other.is_nested();
160
161        if !is_numeric && !is_nested {
162            return self;
163        }
164
165        match (self, other) {
166            (Bool(a), Bool(b)) => Bool(a || b),
167            (Vector(a), Vector(b)) => Vector(a.into_iter().zip(b).map(|(x, y)| x + y).collect()),
168            (Dict(a), Dict(b)) => {
169                if a.len() != b.len() {
170                    return Null;
171                }
172
173                Dict(
174                    a.into_iter()
175                        .zip(b)
176                        .map(|(one, two)| {
177                            if one.0 != two.0 {
178                                return (one.0, one.1, Null);
179                            }
180
181                            (one.0, one.1, one.2 + two.2)
182                        })
183                        .collect(),
184                )
185            }
186            (lhs, rhs) => bin_numeric_op!(lhs, rhs, +),
187        }
188    }
189}
190
191impl Sub for AnyValue<'_> {
192    type Output = Self;
193
194    #[inline(always)]
195    fn sub(self, other: Self) -> Self {
196        use AnyValue::*;
197
198        let is_numeric = self.dtype().is_numeric() && other.dtype().is_numeric();
199        let is_nested = self.is_nested() && other.is_nested();
200
201        if !is_numeric && !is_nested {
202            return self;
203        }
204
205        match (self, other) {
206            (Bool(a), Bool(b)) => Bool(a ^ b),
207            (Vector(a), Vector(b)) => Vector(a.into_iter().zip(b).map(|(x, y)| x - y).collect()),
208            (Dict(a), Dict(b)) => {
209                if a.len() != b.len() {
210                    return Null;
211                }
212
213                Dict(
214                    a.into_iter()
215                        .zip(b)
216                        .map(|(one, two)| {
217                            if one.0 != two.0 {
218                                return (one.0, one.1, Null);
219                            }
220
221                            (one.0, one.1, one.2 - two.2)
222                        })
223                        .collect(),
224                )
225            }
226            (lhs, rhs) => bin_numeric_op!(lhs, rhs, -),
227        }
228    }
229}
230
231impl Mul for AnyValue<'_> {
232    type Output = Self;
233
234    #[inline(always)]
235    fn mul(self, other: Self) -> Self {
236        use AnyValue::*;
237
238        let is_numeric = self.dtype().is_numeric() && other.dtype().is_numeric();
239        let is_nested = self.is_nested() && other.is_nested();
240
241        if !is_numeric && !is_nested {
242            return self;
243        }
244
245        match (self, other) {
246            (Bool(a), Bool(b)) => Bool(a && b),
247            (Vector(a), Vector(b)) => Vector(a.into_iter().zip(b).map(|(x, y)| x * y).collect()),
248            (Dict(a), Dict(b)) => {
249                if a.len() != b.len() {
250                    return Null;
251                }
252
253                Dict(
254                    a.into_iter()
255                        .zip(b)
256                        .map(|(one, two)| {
257                            if one.0 != two.0 {
258                                return (one.0, one.1, Null);
259                            }
260
261                            (one.0, one.1, one.2 * two.2)
262                        })
263                        .collect(),
264                )
265            }
266            (lhs, rhs) => bin_numeric_op!(lhs, rhs, *),
267        }
268    }
269}
270
271impl Div for AnyValue<'_> {
272    type Output = Self;
273
274    #[inline(always)]
275    fn div(self, other: Self) -> Self {
276        use AnyValue::*;
277
278        let is_numeric = self.dtype().is_numeric() && other.dtype().is_numeric();
279        let is_nested = self.is_nested() && other.is_nested();
280
281        if !is_numeric && !is_nested {
282            return self;
283        }
284
285        match (self, other) {
286            (Vector(a), Vector(b)) => Vector(a.into_iter().zip(b).map(|(x, y)| x / y).collect()),
287            (Dict(a), Dict(b)) => {
288                if a.len() != b.len() {
289                    return Null;
290                }
291
292                Dict(
293                    a.into_iter()
294                        .zip(b)
295                        .map(|(one, two)| {
296                            if one.0 != two.0 {
297                                return (one.0, one.1, Null);
298                            }
299
300                            (one.0, one.1, one.2 / two.2)
301                        })
302                        .collect(),
303                )
304            }
305            (lhs, rhs) => bin_numeric_div!(lhs, rhs),
306        }
307    }
308}
309
310impl Rem for AnyValue<'_> {
311    type Output = Self;
312
313    #[inline(always)]
314    fn rem(self, other: Self) -> Self {
315        use AnyValue::*;
316
317        let is_numeric = self.dtype().is_numeric() && other.dtype().is_numeric();
318
319        if !is_numeric {
320            return self;
321        }
322
323        match (self, other) {
324            (Vector(a), Vector(b)) => Vector(a.into_iter().zip(b).map(|(x, y)| x % y).collect()),
325            (Dict(a), Dict(b)) => {
326                if a.len() != b.len() {
327                    return Null;
328                }
329
330                Dict(
331                    a.into_iter()
332                        .zip(b)
333                        .map(|(one, two)| {
334                            if one.0 != two.0 {
335                                return (one.0, one.1, Null);
336                            }
337
338                            (one.0, one.1, one.2 % two.2)
339                        })
340                        .collect(),
341                )
342            }
343            (lhs, rhs) => bin_numeric_op!(lhs, rhs, %),
344        }
345    }
346}
347
348impl<'a> BitAnd for AnyValue<'a> {
349    type Output = AnyValue<'static>;
350
351    fn bitand(self, rhs: Self) -> Self::Output {
352        match (self, rhs) {
353            (AnyValue::Bool(a), AnyValue::Bool(b)) => AnyValue::Bool(a & b),
354            _ => AnyValue::Null,
355        }
356    }
357}
358
359impl<'a> BitOr for AnyValue<'a> {
360    type Output = AnyValue<'static>;
361
362    fn bitor(self, rhs: Self) -> Self::Output {
363        match (self, rhs) {
364            (AnyValue::Bool(a), AnyValue::Bool(b)) => AnyValue::Bool(a | b),
365            _ => AnyValue::Null,
366        }
367    }
368}
369
370impl<'a> Not for AnyValue<'a> {
371    type Output = AnyValue<'static>;
372
373    fn not(self) -> Self::Output {
374        match self {
375            AnyValue::Bool(v) => AnyValue::Bool(!v),
376            _ => AnyValue::Null,
377        }
378    }
379}
380
381#[inline]
382pub fn pow_anyvalue(
383    base: &AnyValue<'_>,
384    exp: &AnyValue<'_>,
385) -> Result<AnyValue<'static>, radiate_error::RadiateError> {
386    use AnyValue::*;
387    match (base, exp) {
388        (Int8(a), Int8(b)) => Ok(Int8(a.pow(*b as u32))),
389        (Int16(a), Int8(b)) => Ok(Int16(a.pow(*b as u32))),
390        (Int32(a), Int8(b)) => Ok(Int32(a.pow(*b as u32))),
391        (Int64(a), Int8(b)) => Ok(Int64(a.pow(*b as u32))),
392        (Int128(a), Int8(b)) => Ok(Int128(a.pow(*b as u32))),
393
394        (Int16(a), Int16(b)) => Ok(Int16(a.pow(*b as u32))),
395        (Int32(a), Int16(b)) => Ok(Int32(a.pow(*b as u32))),
396        (Int64(a), Int16(b)) => Ok(Int64(a.pow(*b as u32))),
397        (Int128(a), Int16(b)) => Ok(Int128(a.pow(*b as u32))),
398
399        (Int32(a), Int32(b)) => Ok(Int32(a.pow(*b as u32))),
400        (Int64(a), Int32(b)) => Ok(Int64(a.pow(*b as u32))),
401        (Int128(a), Int32(b)) => Ok(Int128(a.pow(*b as u32))),
402
403        (Int64(a), Int64(b)) => Ok(Int64(a.pow(*b as u32))),
404        (Int128(a), Int64(b)) => Ok(Int128(a.pow(*b as u32))),
405
406        (Int128(a), Int128(b)) => Ok(Int128(a.pow(*b as u32))),
407
408        (UInt8(a), UInt8(b)) => Ok(UInt8(a.pow(*b as u32))),
409        (UInt8(a), UInt16(b)) => Ok(UInt16((u16::from(*a)).pow(u32::from(*b)))),
410        (UInt8(a), UInt32(b)) => Ok(UInt32((u32::from(*a)).pow(*b))),
411
412        (UInt16(a), UInt16(b)) => Ok(UInt16(a.pow(*b as u32))),
413        (UInt16(a), UInt32(b)) => Ok(UInt32((u32::from(*a)).pow(*b))),
414
415        (UInt32(a), UInt32(b)) => Ok(UInt32(a.pow(*b))),
416
417        (UInt64(a), UInt64(b)) => Ok(UInt64(a.pow(*b as u32))),
418
419        (UInt128(a), UInt128(b)) => Ok(UInt128(a.pow(*b as u32))),
420
421        (Float32(a), Float32(b)) => Ok(Float32(a.powf(*b))),
422        (Float32(a), Float64(b)) => Ok(Float64((*a as f64).powf(*b))),
423
424        (Float64(a), Float32(b)) => Ok(Float64(a.powf(*b as f64))),
425        (Float64(a), Float64(b)) => Ok(Float64(a.powf(*b))),
426        _ => {
427            radiate_bail!(Expr: "Exponentiation is only supported for numeric types, got base {:?} and exponent {:?}", base, exp)
428        }
429    }
430}
431
432#[inline]
433#[allow(dead_code)]
434fn mean_anyvalue(one: &AnyValue<'_>, two: &AnyValue<'_>) -> Option<AnyValue<'static>> {
435    use AnyValue::*;
436    if let Some(v) = mean_numeric(one, two) {
437        return Some(v);
438    }
439
440    match (one, two) {
441        (Bool(x), Bool(y)) => Some(Bool(*x && *y)),
442
443        (Vector(xs), Vector(ys)) => super::value::apply_zipped_slice(xs, ys, mean_anyvalue),
444        (Dict(xs), Dict(ys)) => super::value::apply_zipped_struct_slice(xs, ys, mean_anyvalue),
445        _ => None,
446    }
447}
448
449#[inline]
450#[allow(dead_code)]
451fn mean_numeric(a: &AnyValue<'_>, b: &AnyValue<'_>) -> Option<AnyValue<'static>> {
452    use AnyValue::*;
453    let out = match (a, b) {
454        (UInt8(x), UInt8(y)) => UInt8(((u16::from(*x) + u16::from(*y)) / 2) as u8),
455        (UInt16(x), UInt16(y)) => UInt16(((u32::from(*x) + u32::from(*y)) / 2) as u16),
456        (UInt32(x), UInt32(y)) => UInt32(((u64::from(*x) + u64::from(*y)) / 2) as u32),
457        (UInt64(x), UInt64(y)) => UInt64(((u128::from(*x) + u128::from(*y)) / 2) as u64),
458
459        (Int8(x), Int8(y)) => Int8(*x + ((*y as i16 - *x as i16) / 2) as i8),
460        (Int16(x), Int16(y)) => Int16(*x + ((*y as i32 - *x as i32) / 2) as i16),
461        (Int32(x), Int32(y)) => Int32(*x + ((*y as i64 - *x as i64) / 2) as i32),
462        (Int64(x), Int64(y)) => {
463            let dx = (*y as i128) - (*x as i128);
464            Int64(*x + (dx / 2) as i64)
465        }
466        (Int128(x), Int128(y)) => Int128(*x + ((*y - *x) / 2)),
467
468        (Float32(x), Float32(y)) => Float32((*x + *y) / 2.0),
469        (Float64(x), Float64(y)) => Float64((*x + *y) / 2.0),
470
471        _ => return None,
472    };
473
474    Some(out)
475}
476
477#[cfg(test)]
478mod tests {
479    use crate::SmallStr;
480
481    use super::*;
482    use AnyValue::*;
483
484    fn make_vec(xs: Vec<AnyValue<'static>>) -> AnyValue<'static> {
485        AnyValue::Vector(xs)
486    }
487
488    fn make_dict(pairs: Vec<(&'static str, AnyValue<'static>)>) -> AnyValue<'static> {
489        let fields = pairs
490            .into_iter()
491            .map(|(name, val)| (SmallStr::from(name), val.dtype(), val))
492            .collect();
493        AnyValue::Dict(fields)
494    }
495
496    // ---------- Numeric: happy paths (same-type) ----------
497    #[test]
498    fn numeric_add_same_type() {
499        assert_eq!(Bool(true) + Bool(false), Bool(true));
500
501        assert_eq!(UInt8(10) + UInt8(5), UInt8(15));
502        assert_eq!(UInt16(10) + UInt16(5), UInt16(15));
503        assert_eq!(UInt32(10) + UInt32(5), UInt32(15));
504        assert_eq!(UInt64(10) + UInt64(5), UInt64(15));
505
506        assert_eq!(Int8(10) + Int8(5), Int8(15));
507        assert_eq!(Int16(10) + Int16(5), Int16(15));
508        assert_eq!(Int32(10) + Int32(5), Int32(15));
509        assert_eq!(Int64(10) + Int64(5), Int64(15));
510        assert_eq!(Int128(10) + Int128(5), Int128(15));
511
512        assert_eq!(Float32(1.5) + Float32(2.0), Float32(3.5));
513        assert_eq!(Float64(1.5) + Float64(2.0), Float64(3.5));
514    }
515
516    #[test]
517    fn numeric_sub_same_type() {
518        assert_eq!(Bool(true) - Bool(false), Bool(true));
519
520        assert_eq!(UInt8(10) - UInt8(3), UInt8(7));
521        assert_eq!(UInt16(10) - UInt16(3), UInt16(7));
522        assert_eq!(UInt32(10) - UInt32(3), UInt32(7));
523        assert_eq!(UInt64(10) - UInt64(3), UInt64(7));
524
525        assert_eq!(Int8(10) - Int8(4), Int8(6));
526        assert_eq!(Int16(10) - Int16(4), Int16(6));
527        assert_eq!(Int32(10) - Int32(4), Int32(6));
528        assert_eq!(Int64(10) - Int64(4), Int64(6));
529        assert_eq!(Int128(10) - Int128(4), Int128(6));
530
531        assert_eq!(Float32(5.0) - Float32(2.5), Float32(2.5));
532        assert_eq!(Float64(5.0) - Float64(2.5), Float64(2.5));
533    }
534
535    #[test]
536    fn numeric_mul_same_type() {
537        assert_eq!(Bool(true) * Bool(false), Bool(true));
538
539        assert_eq!(UInt8(7) * UInt8(6), UInt8(42));
540        assert_eq!(UInt16(7) * UInt16(6), UInt16(42));
541        assert_eq!(UInt32(7) * UInt32(6), UInt32(42));
542        assert_eq!(UInt64(7) * UInt64(6), UInt64(42));
543
544        assert_eq!(Int8(7) * Int8(6), Int8(42));
545        assert_eq!(Int16(7) * Int16(6), Int16(42));
546        assert_eq!(Int32(7) * Int32(6), Int32(42));
547        assert_eq!(Int64(7) * Int64(6), Int64(42));
548        assert_eq!(Int128(7) * Int128(6), Int128(42));
549
550        assert_eq!(Float32(1.5) * Float32(2.0), Float32(3.0));
551        assert_eq!(Float64(1.5) * Float64(2.0), Float64(3.0));
552    }
553
554    #[test]
555    fn numeric_div_same_type() {
556        assert_eq!(Bool(true) / Bool(false), Bool(true));
557
558        assert_eq!(UInt8(42) / UInt8(6), UInt8(7));
559        assert_eq!(UInt16(42) / UInt16(6), UInt16(7));
560        assert_eq!(UInt32(42) / UInt32(6), UInt32(7));
561        assert_eq!(UInt64(42) / UInt64(6), UInt64(7));
562
563        assert_eq!(Int8(42) / Int8(6), Int8(7));
564        assert_eq!(Int16(42) / Int16(6), Int16(7));
565        assert_eq!(Int32(42) / Int32(6), Int32(7));
566        assert_eq!(Int64(42) / Int64(6), Int64(7));
567        assert_eq!(Int128(42) / Int128(6), Int128(7));
568
569        assert_eq!(Float32(7.5) / Float32(2.5), Float32(3.0));
570        assert_eq!(Float64(7.5) / Float64(2.5), Float64(3.0));
571    }
572
573    #[test]
574    fn int_div_by_zero_yields_null() {
575        assert_eq!(Int32(5) / Int32(0), Int32(5));
576        assert_eq!(UInt64(7) / UInt64(0), UInt64(7));
577    }
578
579    // ---------- Vector elementwise ----------
580    #[test]
581    fn vector_elementwise_add_ok() {
582        let a = make_vec(vec![Int32(1), Int32(2), Int32(3)]);
583        let b = make_vec(vec![Int32(4), Int32(5), Int32(6)]);
584        let out = make_vec(vec![Int32(5), Int32(7), Int32(9)]);
585        assert_eq!(a + b, out);
586    }
587
588    #[test]
589    fn vector_length_mismatch() {
590        let a = make_vec(vec![Int32(1), Int32(2)]);
591        let b = make_vec(vec![Int32(3)]);
592        assert_eq!(a + b, Vector(vec![Int32(4)]));
593    }
594
595    // ---------- Struct fieldwise ----------
596    #[test]
597    fn struct_same_shape_by_order() {
598        // Current code: length check; name mismatch → per-field Null (keeps left field)
599        let a = make_dict(vec![("x", Int32(1)), ("y", Int32(2))]);
600        let b = make_dict(vec![("x", Int32(3)), ("y", Int32(4))]);
601        let out = make_dict(vec![("x", Int32(4)), ("y", Int32(6))]);
602        assert_eq!(a + b, out);
603    }
604
605    #[test]
606    fn struct_length_mismatch_yields_null() {
607        let a = make_dict(vec![("x", Int32(1))]);
608        let b = make_dict(vec![("x", Int32(2)), ("y", Int32(3))]);
609        assert_eq!(a + b, Null);
610    }
611
612    #[test]
613    fn struct_field_name_mismatch_sets_field_null_under_current_rules() {
614        let a = make_dict(vec![("x", Int32(1)), ("y", Int32(2))]);
615        let b = make_dict(vec![("x", Int32(3)), ("z", Int32(9))]);
616        // Current impl: when names differ at a position, that *slot* becomes Null; rest proceed.
617        let expected = make_dict(vec![("x", Int32(4)), ("y", Null)]);
618        assert_eq!(a + b, expected);
619    }
620
621    #[test]
622    fn struct_align_by_name_regardless_of_order() {
623        let a = make_dict(vec![("x", Int32(1)), ("y", Int32(2))]);
624        let b = make_dict(vec![("y", Int32(4)), ("x", Int32(3))]);
625        let out = make_dict(vec![("x", Null), ("y", Null)]);
626        assert_eq!(a + b, out);
627    }
628
629    // ---------- Null interactions ----------
630    #[test]
631    fn null_propagation() {
632        assert_eq!(Null + Int32(5), Null);
633        assert_eq!(Float64(2.0) * Null, Float64(2.0));
634        assert_eq!(Null / Null, Null);
635    }
636
637    // ---------- Mean ----------
638    #[test]
639    fn mean_numeric_pairs() {
640        assert_eq!(mean_anyvalue(&Int32(2), &Int32(4)), Some(Int32(3)));
641        assert_eq!(mean_anyvalue(&UInt8(10), &UInt8(20)), Some(UInt8(15)));
642        assert_eq!(
643            mean_anyvalue(&Float64(1.0), &Float64(3.0)),
644            Some(Float64(2.0))
645        );
646    }
647
648    #[test]
649    fn mean_bool_is_and() {
650        assert_eq!(mean_anyvalue(&Bool(true), &Bool(false)), Some(Bool(false)));
651        assert_eq!(mean_anyvalue(&Bool(true), &Bool(true)), Some(Bool(true)));
652    }
653
654    // ---------- Algebraic sanity checks ----------
655    #[test]
656    fn add_commutative_for_numeric() {
657        assert_eq!(Int64(7) + Int64(5), Int64(5) + Int64(7));
658    }
659
660    #[test]
661    fn mul_commutative_for_numeric() {
662        assert_eq!(Int16(3) * Int16(9), Int16(9) * Int16(3));
663    }
664
665    #[test]
666    fn sub_non_commutative_for_numeric() {
667        assert_ne!(Int32(10) - Int32(4), Int32(4) - Int32(10));
668    }
669}