1use crate::value::{
5 Value,
6 decimal::Decimal,
7 int::Int,
8 is::IsNumber,
9 number::{
10 promote::Promote,
11 safe::{add::SafeAdd, div::SafeDiv, mul::SafeMul, remainder::SafeRemainder, sub::SafeSub},
12 },
13 to_value::ToValue,
14 uint::Uint,
15 value_type::ValueType,
16};
17
18fn arith_type(v: &Value) -> ValueType {
19 match v {
20 Value::None {
21 inner,
22 } => inner.clone(),
23 other => other.get_type(),
24 }
25}
26
27fn none_inner(l: &Value, r: &Value) -> Option<ValueType> {
28 let l_none = matches!(l, Value::None { .. });
29 let r_none = matches!(r, Value::None { .. });
30 if !l_none && !r_none {
31 return None;
32 }
33 Some(ValueType::promote(arith_type(l), arith_type(r)))
34}
35
36fn value_is_zero(v: &Value) -> bool {
37 match v {
38 Value::Int1(x) => SafeDiv::is_zero(x),
39 Value::Int2(x) => SafeDiv::is_zero(x),
40 Value::Int4(x) => SafeDiv::is_zero(x),
41 Value::Int8(x) => SafeDiv::is_zero(x),
42 Value::Int16(x) => SafeDiv::is_zero(x),
43 Value::Uint1(x) => SafeDiv::is_zero(x),
44 Value::Uint2(x) => SafeDiv::is_zero(x),
45 Value::Uint4(x) => SafeDiv::is_zero(x),
46 Value::Uint8(x) => SafeDiv::is_zero(x),
47 Value::Uint16(x) => SafeDiv::is_zero(x),
48 Value::Float4(x) => SafeDiv::is_zero(&x.value()),
49 Value::Float8(x) => SafeDiv::is_zero(&x.value()),
50 Value::Int(x) => SafeDiv::is_zero(x),
51 Value::Uint(x) => SafeDiv::is_zero(x),
52 Value::Decimal(x) => SafeDiv::is_zero(x),
53 _ => false,
54 }
55}
56
57macro_rules! gen_helpers {
58 ($checked:ident, $sat:ident, $wrap:ident, $trait:ident, $cm:ident, $sm:ident, $wm:ident) => {
59 fn $checked<L, R>(l: &L, r: &R) -> Option<Value>
60 where
61 L: Promote<R>,
62 R: IsNumber,
63 <L as Promote<R>>::Output: $trait,
64 {
65 let (a, b) = l.checked_promote(r)?;
66 a.$cm(&b).map(|o| o.to_value())
67 }
68
69 fn $sat<L, R>(l: &L, r: &R) -> Value
70 where
71 L: Promote<R>,
72 R: IsNumber,
73 <L as Promote<R>>::Output: $trait,
74 {
75 let (a, b) = l.saturating_promote(r);
76 a.$sm(&b).to_value()
77 }
78
79 fn $wrap<L, R>(l: &L, r: &R) -> Value
80 where
81 L: Promote<R>,
82 R: IsNumber,
83 <L as Promote<R>>::Output: $trait,
84 {
85 let (a, b) = l.wrapping_promote(r);
86 a.$wm(&b).to_value()
87 }
88 };
89}
90
91gen_helpers!(v_checked_add, v_sat_add, v_wrap_add, SafeAdd, checked_add, saturating_add, wrapping_add);
92gen_helpers!(v_checked_sub, v_sat_sub, v_wrap_sub, SafeSub, checked_sub, saturating_sub, wrapping_sub);
93gen_helpers!(v_checked_mul, v_sat_mul, v_wrap_mul, SafeMul, checked_mul, saturating_mul, wrapping_mul);
94gen_helpers!(v_checked_rem, v_sat_rem, v_wrap_rem, SafeRemainder, checked_rem, saturating_rem, wrapping_rem);
95
96trait DivToValue: Sized {
97 fn checked_div_to_value(&self, r: &Self) -> Option<Value>;
98 fn saturating_div_to_value(&self, r: &Self) -> Value;
99 fn wrapping_div_to_value(&self, r: &Self) -> Value;
100}
101
102macro_rules! impl_div_to_value_via_decimal {
103 ($($t:ty),*) => {
104 $(
105 impl DivToValue for $t {
106 fn checked_div_to_value(&self, r: &Self) -> Option<Value> {
107 Decimal::from(self.clone()).checked_div(&Decimal::from(r.clone())).map(Value::Decimal)
108 }
109 fn saturating_div_to_value(&self, r: &Self) -> Value {
110 Value::Decimal(Decimal::from(self.clone()).saturating_div(&Decimal::from(r.clone())))
111 }
112 fn wrapping_div_to_value(&self, r: &Self) -> Value {
113 Value::Decimal(Decimal::from(self.clone()).wrapping_div(&Decimal::from(r.clone())))
114 }
115 }
116 )*
117 };
118}
119
120impl_div_to_value_via_decimal!(i128, u128, Int, Uint);
121
122impl DivToValue for f64 {
123 fn checked_div_to_value(&self, r: &Self) -> Option<Value> {
124 self.checked_div(r).map(|o| o.to_value())
125 }
126 fn saturating_div_to_value(&self, r: &Self) -> Value {
127 self.saturating_div(r).to_value()
128 }
129 fn wrapping_div_to_value(&self, r: &Self) -> Value {
130 self.wrapping_div(r).to_value()
131 }
132}
133
134impl DivToValue for Decimal {
135 fn checked_div_to_value(&self, r: &Self) -> Option<Value> {
136 self.checked_div(r).map(Value::Decimal)
137 }
138 fn saturating_div_to_value(&self, r: &Self) -> Value {
139 Value::Decimal(self.saturating_div(r))
140 }
141 fn wrapping_div_to_value(&self, r: &Self) -> Value {
142 Value::Decimal(self.wrapping_div(r))
143 }
144}
145
146fn v_checked_div<L, R>(l: &L, r: &R) -> Option<Value>
147where
148 L: Promote<R>,
149 R: IsNumber,
150 <L as Promote<R>>::Output: DivToValue,
151{
152 let (a, b) = l.checked_promote(r)?;
153 a.checked_div_to_value(&b)
154}
155
156fn v_sat_div<L, R>(l: &L, r: &R) -> Value
157where
158 L: Promote<R>,
159 R: IsNumber,
160 <L as Promote<R>>::Output: DivToValue,
161{
162 let (a, b) = l.saturating_promote(r);
163 a.saturating_div_to_value(&b)
164}
165
166fn v_wrap_div<L, R>(l: &L, r: &R) -> Value
167where
168 L: Promote<R>,
169 R: IsNumber,
170 <L as Promote<R>>::Output: DivToValue,
171{
172 let (a, b) = l.wrapping_promote(r);
173 a.wrapping_div_to_value(&b)
174}
175
176macro_rules! right_arms {
177 ($r:expr, $la:expr, $op:ident, $fallback:expr) => {
178 match $r {
179 Value::Int1(b) => $op($la, b),
180 Value::Int2(b) => $op($la, b),
181 Value::Int4(b) => $op($la, b),
182 Value::Int8(b) => $op($la, b),
183 Value::Int16(b) => $op($la, b),
184 Value::Uint1(b) => $op($la, b),
185 Value::Uint2(b) => $op($la, b),
186 Value::Uint4(b) => $op($la, b),
187 Value::Uint8(b) => $op($la, b),
188 Value::Uint16(b) => $op($la, b),
189 Value::Int(b) => $op($la, b),
190 Value::Uint(b) => $op($la, b),
191 Value::Decimal(b) => $op($la, b),
192 Value::Float4(b) => $op($la, &b.value()),
193 Value::Float8(b) => $op($la, &b.value()),
194 _ => $fallback,
195 }
196 };
197}
198
199macro_rules! value_arith_dispatch {
200 ($l:expr, $r:expr, $op:ident, $fallback:expr) => {
201 match $l {
202 Value::Int1(a) => right_arms!($r, a, $op, $fallback),
203 Value::Int2(a) => right_arms!($r, a, $op, $fallback),
204 Value::Int4(a) => right_arms!($r, a, $op, $fallback),
205 Value::Int8(a) => right_arms!($r, a, $op, $fallback),
206 Value::Int16(a) => right_arms!($r, a, $op, $fallback),
207 Value::Uint1(a) => right_arms!($r, a, $op, $fallback),
208 Value::Uint2(a) => right_arms!($r, a, $op, $fallback),
209 Value::Uint4(a) => right_arms!($r, a, $op, $fallback),
210 Value::Uint8(a) => right_arms!($r, a, $op, $fallback),
211 Value::Uint16(a) => right_arms!($r, a, $op, $fallback),
212 Value::Int(a) => right_arms!($r, a, $op, $fallback),
213 Value::Uint(a) => right_arms!($r, a, $op, $fallback),
214 Value::Decimal(a) => right_arms!($r, a, $op, $fallback),
215 Value::Float4(a) => right_arms!($r, &a.value(), $op, $fallback),
216 Value::Float8(a) => right_arms!($r, &a.value(), $op, $fallback),
217 _ => $fallback,
218 }
219 };
220}
221
222macro_rules! impl_value_safe {
223 ($trait:ident, $checked:ident, $sat:ident, $wrap:ident, $hc:ident, $hs:ident, $hw:ident) => {
224 impl $trait for Value {
225 fn $checked(&self, r: &Self) -> Option<Self> {
226 if let Some(inner) = none_inner(self, r) {
227 return Some(Value::None {
228 inner,
229 });
230 }
231 value_arith_dispatch!(self, r, $hc, None)
232 }
233
234 fn $sat(&self, r: &Self) -> Self {
235 if let Some(inner) = none_inner(self, r) {
236 return Value::None {
237 inner,
238 };
239 }
240 value_arith_dispatch!(
241 self,
242 r,
243 $hs,
244 Value::None {
245 inner: ValueType::Any
246 }
247 )
248 }
249
250 fn $wrap(&self, r: &Self) -> Self {
251 if let Some(inner) = none_inner(self, r) {
252 return Value::None {
253 inner,
254 };
255 }
256 value_arith_dispatch!(
257 self,
258 r,
259 $hw,
260 Value::None {
261 inner: ValueType::Any
262 }
263 )
264 }
265 }
266 };
267}
268
269impl_value_safe!(SafeAdd, checked_add, saturating_add, wrapping_add, v_checked_add, v_sat_add, v_wrap_add);
270impl_value_safe!(SafeSub, checked_sub, saturating_sub, wrapping_sub, v_checked_sub, v_sat_sub, v_wrap_sub);
271impl_value_safe!(SafeMul, checked_mul, saturating_mul, wrapping_mul, v_checked_mul, v_sat_mul, v_wrap_mul);
272
273impl SafeDiv for Value {
274 fn checked_div(&self, r: &Self) -> Option<Self> {
275 if let Some(inner) = none_inner(self, r) {
276 return Some(Value::None {
277 inner,
278 });
279 }
280 value_arith_dispatch!(self, r, v_checked_div, None)
281 }
282
283 fn saturating_div(&self, r: &Self) -> Self {
284 if let Some(inner) = none_inner(self, r) {
285 return Value::None {
286 inner,
287 };
288 }
289 value_arith_dispatch!(
290 self,
291 r,
292 v_sat_div,
293 Value::None {
294 inner: ValueType::Any
295 }
296 )
297 }
298
299 fn wrapping_div(&self, r: &Self) -> Self {
300 if let Some(inner) = none_inner(self, r) {
301 return Value::None {
302 inner,
303 };
304 }
305 value_arith_dispatch!(
306 self,
307 r,
308 v_wrap_div,
309 Value::None {
310 inner: ValueType::Any
311 }
312 )
313 }
314
315 fn is_zero(&self) -> bool {
316 value_is_zero(self)
317 }
318}
319
320impl SafeRemainder for Value {
321 fn checked_rem(&self, r: &Self) -> Option<Self> {
322 if let Some(inner) = none_inner(self, r) {
323 return Some(Value::None {
324 inner,
325 });
326 }
327 value_arith_dispatch!(self, r, v_checked_rem, None)
328 }
329
330 fn saturating_rem(&self, r: &Self) -> Self {
331 if let Some(inner) = none_inner(self, r) {
332 return Value::None {
333 inner,
334 };
335 }
336 value_arith_dispatch!(
337 self,
338 r,
339 v_sat_rem,
340 Value::None {
341 inner: ValueType::Any
342 }
343 )
344 }
345
346 fn wrapping_rem(&self, r: &Self) -> Self {
347 if let Some(inner) = none_inner(self, r) {
348 return Value::None {
349 inner,
350 };
351 }
352 value_arith_dispatch!(
353 self,
354 r,
355 v_wrap_rem,
356 Value::None {
357 inner: ValueType::Any
358 }
359 )
360 }
361
362 fn is_zero(&self) -> bool {
363 value_is_zero(self)
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370 use crate::value::{ordered_f32::OrderedF32, ordered_f64::OrderedF64};
371
372 fn int4(v: i32) -> Value {
373 Value::Int4(v)
374 }
375 fn int2(v: i16) -> Value {
376 Value::Int2(v)
377 }
378 fn uint4(v: u32) -> Value {
379 Value::Uint4(v)
380 }
381 fn dec(v: i64) -> Value {
382 Value::Decimal(Decimal::from(v))
383 }
384
385 #[test]
386 fn add_int_pair_promotes_to_int16() {
387 assert_eq!(int2(3).checked_add(&int4(4)), Some(Value::Int16(7)));
390 assert_eq!(int4(3).checked_add(&int4(4)), Some(Value::Int16(7)));
391 }
392
393 #[test]
394 fn add_uint_pair_promotes_to_uint16() {
395 assert_eq!(uint4(3).checked_add(&uint4(4)), Some(Value::Uint16(7)));
396 }
397
398 #[test]
399 fn add_float_pair_is_float8() {
400 let out = Value::Float8(OrderedF64::try_from(1.5).unwrap())
401 .checked_add(&Value::Float4(OrderedF32::try_from(2.5f32).unwrap()));
402 assert_eq!(out, Some(Value::Float8(OrderedF64::try_from(4.0).unwrap())));
403 }
404
405 #[test]
406 fn add_decimal_pair_is_decimal() {
407 assert_eq!(dec(3).checked_add(&dec(4)), Some(Value::Decimal(Decimal::from(7i64))));
408 }
409
410 #[test]
411 fn mixed_int_decimal_is_decimal() {
412 assert_eq!(int4(3).checked_add(&dec(4)), Some(Value::Decimal(Decimal::from(7i64))));
413 }
414
415 #[test]
416 fn int_div_is_decimal_exact() {
417 let got = int4(3).checked_div(&int4(2)).unwrap();
419 assert_eq!(got, Value::Decimal(Decimal::from(3i64).checked_div(&Decimal::from(2i64)).unwrap()));
420 assert_ne!(got, Value::Decimal(Decimal::from(1i64)));
421 }
422
423 #[test]
424 fn float_div_stays_float8() {
425 let three = Value::Float8(OrderedF64::try_from(3.0).unwrap());
426 let two = Value::Float8(OrderedF64::try_from(2.0).unwrap());
427 assert_eq!(three.checked_div(&two), Some(Value::Float8(OrderedF64::try_from(1.5).unwrap())));
428 }
429
430 #[test]
431 fn none_propagates_with_promoted_inner() {
432 let got = Value::none_of(ValueType::Int4).checked_add(&int4(5));
435 assert_eq!(
436 got,
437 Some(Value::None {
438 inner: ValueType::promote(ValueType::Int4, ValueType::Int4)
439 })
440 );
441 assert!(matches!(got, Some(Value::None { .. })));
442 assert!(matches!(int4(5).checked_add(&Value::none_of(ValueType::Int4)), Some(Value::None { .. })));
443 assert!(matches!(
444 Value::none_of(ValueType::Int4).checked_add(&Value::none_of(ValueType::Int8)),
445 Some(Value::None { .. })
446 ));
447 }
448
449 #[test]
450 fn overflow_checked_saturating_wrapping() {
451 let max = Value::Int16(i128::MAX);
454 let one = Value::Int16(1);
455 assert_eq!(max.checked_add(&one), None);
456 assert_eq!(max.saturating_add(&one), Value::Int16(i128::MAX));
457 assert_eq!(max.wrapping_add(&one), Value::Int16(i128::MIN));
458 }
459
460 #[test]
461 fn div_by_zero_is_none() {
462 assert_eq!(int4(3).checked_div(&int4(0)), None);
463 assert_eq!(dec(3).checked_div(&dec(0)), None);
464 let one = Value::Float8(OrderedF64::try_from(1.0).unwrap());
465 let zero = Value::Float8(OrderedF64::try_from(0.0).unwrap());
466 assert_eq!(one.checked_div(&zero), None);
467 }
468
469 #[test]
470 fn is_zero_per_variant() {
471 assert!(SafeDiv::is_zero(&int4(0)));
472 assert!(!SafeDiv::is_zero(&int4(1)));
473 assert!(SafeDiv::is_zero(&dec(0)));
474 assert!(!SafeDiv::is_zero(&dec(5)));
475 assert!(!SafeDiv::is_zero(&Value::none_of(ValueType::Int4)));
476 assert!(!SafeDiv::is_zero(&Value::Boolean(true)));
477 }
478
479 #[test]
480 fn non_numeric_operand_is_none() {
481 assert_eq!(int4(3).checked_add(&Value::Boolean(true)), None);
482 assert_eq!(Value::Utf8("x".into()).checked_add(&int4(3)), None);
483 }
484
485 #[test]
486 fn retraction_invariant_exact_for_integers() {
487 let cases = [
490 (int4(100), int4(7)),
491 (int2(30), int4(9)),
492 (uint4(50), uint4(8)),
493 (dec(1000), dec(123)),
494 (Value::Int(Int::from_i64(99)), Value::Int(Int::from_i64(40))),
495 ];
496 for (r, x) in cases {
497 let added = r.checked_add(&x).unwrap();
498 let restored = added.checked_sub(&x).unwrap();
499 let twice = restored.checked_add(&x).unwrap();
502 assert_eq!(added, twice, "add/sub must invert for {r:?} - {x:?}");
503 }
504 }
505
506 #[test]
507 fn retraction_float_within_tolerance() {
508 let r = Value::Float8(OrderedF64::try_from(0.1).unwrap());
511 let x = Value::Float8(OrderedF64::try_from(0.2).unwrap());
512 let restored = r.checked_add(&x).unwrap().checked_sub(&x).unwrap();
513 if let Value::Float8(v) = restored {
514 assert!((v.value() - 0.1).abs() < 1e-9, "float retraction drift too large: {}", v.value());
515 } else {
516 panic!("expected Float8, got {restored:?}");
517 }
518 }
519
520 #[test]
521 fn nan_result_is_none_float8() {
522 let inf = Value::Float8(OrderedF64::try_from(f64::MAX).unwrap());
525 assert_eq!(inf.checked_mul(&inf), None);
526 }
527}