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