reifydb_core/value/column/arith/
scalar.rs1use arrow_buffer::i256;
5use reifydb_value::{
6 Result,
7 error::{Error, TypeError},
8 fragment::LazyFragment,
9 value::{
10 constraint::precision::Precision,
11 decimal::{Decimal, unscaled},
12 is::IsNumber,
13 number::{
14 promote::Promote,
15 safe::{add::SafeAdd, div::SafeDiv, mul::SafeMul, remainder::SafeRemainder, sub::SafeSub},
16 },
17 value_type::{ValueType, get::GetType},
18 },
19};
20
21use crate::{
22 interface::catalog::property::ColumnSaturationStrategy,
23 value::column::arith::context::{ArithContext, Overflow},
24};
25
26enum Mode {
27 Error,
28 None,
29 Saturate,
30 Wrap,
31}
32
33impl ArithContext {
34 fn mode(&self) -> Mode {
35 match self.overflow {
36 Overflow::Target => match self.saturation_policy() {
37 ColumnSaturationStrategy::Error => Mode::Error,
38 ColumnSaturationStrategy::None => Mode::None,
39 },
40 Overflow::Error => Mode::Error,
41 Overflow::None => Mode::None,
42 Overflow::Saturate => Mode::Saturate,
43 Overflow::Wrap => Mode::Wrap,
44 }
45 }
46
47 fn out_of_range(&self, target: ValueType, fragment: impl LazyFragment) -> Error {
48 TypeError::NumberOutOfRange {
49 target,
50 fragment: fragment.fragment(),
51 descriptor: self.target.as_ref().and_then(|c| c.to_number_descriptor()),
52 }
53 .into()
54 }
55}
56
57macro_rules! impl_scalar_op {
58 ($method:ident, $safe_trait:ident, $checked:ident, $saturating:ident, $wrapping:ident $(, $zero:ident)?) => {
59 impl ArithContext {
60 pub fn $method<L, R>(
61 &self,
62 l: &L,
63 r: &R,
64 fragment: impl LazyFragment + Copy,
65 ) -> Result<Option<<L as Promote<R>>::Output>>
66 where
67 L: Promote<R>,
68 R: IsNumber,
69 <L as Promote<R>>::Output: IsNumber,
70 <L as Promote<R>>::Output: $safe_trait,
71 {
72 let target = || <L as Promote<R>>::Output::get_type();
73 let mode = self.mode();
74 let Some((lp, rp)) = l.checked_promote(r) else {
75 return match mode {
76 Mode::None => Ok(None),
77 _ => Err(self.out_of_range(target(), fragment)),
78 };
79 };
80 match mode {
81 Mode::Error => {
82 $(
83 if <<L as Promote<R>>::Output as $safe_trait>::$zero(&rp) {
84 return Err(TypeError::DivisionByZero {
85 target: target(),
86 fragment: fragment.fragment(),
87 }
88 .into());
89 }
90 )?
91 lp.$checked(&rp).ok_or_else(|| self.out_of_range(target(), fragment)).map(Some)
92 }
93 Mode::None => Ok(lp.$checked(&rp)),
94 Mode::Saturate => Ok(Some(lp.$saturating(&rp))),
95 Mode::Wrap => Ok(Some(lp.$wrapping(&rp))),
96 }
97 }
98 }
99 };
100}
101
102pub trait FitFamily: Sized {
103 fn fit_family(&self, target: &ValueType) -> Option<Self>;
104
105 fn clamp_family(&self, target: &ValueType) -> Self;
106}
107
108impl FitFamily for Decimal {
109 fn fit_family(&self, target: &ValueType) -> Option<Self> {
110 let (Some(precision), Some(scale)) = (target.precision(), target.scale()) else {
111 return Some(self.clone());
112 };
113 let rounded = self.round_to_scale(scale.value())?;
114 (rounded.digits() <= precision.value()).then_some(rounded)
115 }
116
117 fn clamp_family(&self, target: &ValueType) -> Self {
118 let (Some(precision), Some(scale)) = (target.precision(), target.scale()) else {
119 return self.clone();
120 };
121 Decimal::from_parts(family_bound(precision, self.is_negative()), scale.value())
122 .expect("a precision bound is in range")
123 }
124}
125
126fn family_bound(precision: Precision, negative: bool) -> i256 {
127 let max = unscaled::pow10(precision.value()).expect("a precision is at most 76 digits").wrapping_sub(i256::ONE);
128 if negative {
129 max.wrapping_neg()
130 } else {
131 max
132 }
133}
134
135impl ArithContext {
136 pub fn fit_family<T: FitFamily>(
137 &self,
138 value: T,
139 target: &ValueType,
140 fragment: impl LazyFragment + Copy,
141 ) -> Result<Option<T>> {
142 match (value.fit_family(target), self.mode()) {
143 (Some(fitted), _) => Ok(Some(fitted)),
144 (None, Mode::None) => Ok(None),
145 (None, Mode::Error) => Err(self.out_of_range(target.clone(), fragment)),
146 (None, Mode::Saturate | Mode::Wrap) => Ok(Some(value.clamp_family(target))),
147 }
148 }
149}
150
151impl_scalar_op!(add, SafeAdd, checked_add, saturating_add, wrapping_add);
152impl_scalar_op!(sub, SafeSub, checked_sub, saturating_sub, wrapping_sub);
153impl_scalar_op!(mul, SafeMul, checked_mul, saturating_mul, wrapping_mul);
154impl_scalar_op!(div, SafeDiv, checked_div, saturating_div, wrapping_div, is_zero);
155impl_scalar_op!(remainder, SafeRemainder, checked_rem, saturating_rem, wrapping_rem, is_zero);
156
157#[cfg(test)]
158pub mod tests {
159 use reifydb_value::fragment::Fragment;
160
161 use crate::value::column::arith::context::ArithContext;
162
163 #[test]
164 fn test_add() {
165 let test_instance = ArithContext::default();
166 let result = test_instance.add(&1i8, &255i16, Fragment::testing_empty);
167 assert_eq!(result, Ok(Some(256i128)));
168 }
169
170 #[test]
171 fn test_sub() {
172 let test_instance = ArithContext::default();
173 let result = test_instance.sub(&1i8, &255i16, Fragment::testing_empty);
174 assert_eq!(result, Ok(Some(-254i128)));
175 }
176
177 #[test]
178 fn test_mul() {
179 let test_instance = ArithContext::default();
180 let result = test_instance.mul(&23i8, &255i16, Fragment::testing_empty);
181 assert_eq!(result, Ok(Some(5865i128)));
182 }
183
184 #[test]
185 fn test_div() {
186 let test_instance = ArithContext::default();
187 let result = test_instance.div(&120i8, &20i16, Fragment::testing_empty);
188 assert_eq!(result, Ok(Some(6i128)));
189 }
190
191 #[test]
192 fn test_remainder() {
193 let test_instance = ArithContext::default();
194 let result = test_instance.remainder(&120i8, &21i16, Fragment::testing_empty);
195 assert_eq!(result, Ok(Some(15i128)));
196 }
197}