1use crate::base::{
3 math::BigDecimalExt,
4 scalar::{Scalar, ScalarConversionError},
5};
6use alloc::string::{String, ToString};
7use bigdecimal::{BigDecimal, ParseBigDecimalError};
8use serde::{Deserialize, Deserializer, Serialize};
9use snafu::Snafu;
10
11#[derive(Snafu, Debug, PartialEq)]
13pub enum IntermediateDecimalError {
14 #[snafu(display("{error}"))]
16 ParseError {
17 error: ParseBigDecimalError,
19 },
20 #[snafu(display("Value out of range for target type"))]
22 OutOfRange,
23 #[snafu(display("Fractional part of decimal is non-zero"))]
25 LossyCast,
26 #[snafu(display("Conversion to integer failed"))]
28 ConversionFailure,
29}
30
31impl Eq for IntermediateDecimalError {}
32
33#[derive(Snafu, Debug, Eq, PartialEq)]
35pub enum DecimalError {
36 #[snafu(display("Invalid decimal format or value: {error}"))]
37 InvalidDecimal {
41 error: String,
43 },
44
45 #[snafu(display("Decimal precision is not valid: {error}"))]
46 InvalidPrecision {
50 error: String,
52 },
53
54 #[snafu(display("Decimal scale is not valid: {scale}"))]
55 InvalidScale {
58 scale: String,
60 },
61
62 #[snafu(display("Unsupported operation: cannot round decimal: {error}"))]
63 RoundingError {
66 error: String,
68 },
69
70 #[snafu(transparent)]
73 IntermediateDecimalConversionError {
74 source: IntermediateDecimalError,
76 },
77}
78
79pub type DecimalResult<T> = Result<T, DecimalError>;
81
82impl From<DecimalError> for String {
84 fn from(error: DecimalError) -> Self {
85 error.to_string()
86 }
87}
88
89#[derive(Eq, PartialEq, Debug, Clone, Hash, Serialize, Copy)]
90pub struct Precision(u8);
92pub(crate) const MAX_SUPPORTED_PRECISION: u8 = 75;
93
94impl Precision {
95 pub fn new(value: u8) -> Result<Self, DecimalError> {
97 if value > MAX_SUPPORTED_PRECISION || value == 0 {
98 Err(DecimalError::InvalidPrecision {
99 error: value.to_string(),
100 })
101 } else {
102 Ok(Precision(value))
103 }
104 }
105
106 #[must_use]
108 pub fn value(&self) -> u8 {
109 self.0
110 }
111}
112
113impl TryFrom<u64> for Precision {
114 type Error = DecimalError;
115 fn try_from(value: u64) -> Result<Self, Self::Error> {
116 Precision::new(
117 value
118 .try_into()
119 .map_err(|_| DecimalError::InvalidPrecision {
120 error: value.to_string(),
121 })?,
122 )
123 }
124}
125
126impl<'de> Deserialize<'de> for Precision {
128 fn deserialize<D>(deserializer: D) -> Result<Precision, D::Error>
129 where
130 D: Deserializer<'de>,
131 {
132 let value = u8::deserialize(deserializer)?;
134
135 Precision::new(value).map_err(serde::de::Error::custom)
137 }
138}
139
140pub(crate) fn try_convert_intermediate_decimal_to_scalar<S: Scalar>(
158 d: &BigDecimal,
159 target_precision: Precision,
160 target_scale: i8,
161) -> DecimalResult<S> {
162 d.try_into_bigint_with_precision_and_scale(target_precision.value(), target_scale)?
163 .try_into()
164 .map_err(|e: ScalarConversionError| DecimalError::InvalidDecimal {
165 error: e.to_string(),
166 })
167}
168
169#[cfg(test)]
170mod scale_adjust_test {
171
172 use super::*;
173 use crate::base::scalar::test_scalar::TestScalar;
174 use num_bigint::BigInt;
175
176 #[test]
177 fn we_cannot_scale_past_max_precision() {
178 let decimal = "12345678901234567890123456789012345678901234567890123456789012345678900.0"
179 .parse()
180 .unwrap();
181
182 let target_scale = 5;
183
184 assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
185 &decimal,
186 Precision::new(u8::try_from(decimal.precision()).unwrap_or(u8::MAX)).unwrap(),
187 target_scale
188 )
189 .is_err());
190 }
191
192 #[test]
193 fn we_can_match_exact_decimals_from_queries_to_db() {
194 let decimal: BigDecimal = "123.45".parse().unwrap();
195 let target_scale = 2;
196 let target_precision = 20;
197 let big_int =
198 decimal.try_into_bigint_with_precision_and_scale(target_precision, target_scale);
199 let expected_big_int: BigInt = "12345".parse().unwrap();
200 assert_eq!(big_int, Ok(expected_big_int));
201 }
202
203 #[test]
204 fn we_can_match_decimals_with_negative_scale() {
205 let decimal = "120.00".parse().unwrap();
206 let target_scale = -1;
207 let expected = [12, 0, 0, 0];
208 let result = try_convert_intermediate_decimal_to_scalar::<TestScalar>(
209 &decimal,
210 Precision::new(MAX_SUPPORTED_PRECISION).unwrap(),
211 target_scale,
212 )
213 .unwrap();
214 assert_eq!(result, TestScalar::from(expected));
215 }
216
217 #[test]
218 fn we_can_match_integers_with_negative_scale() {
219 let decimal = "12300".parse().unwrap();
220 let target_scale = -2;
221 let expected_limbs = [123, 0, 0, 0];
222
223 let limbs = try_convert_intermediate_decimal_to_scalar::<TestScalar>(
224 &decimal,
225 Precision::new(u8::try_from(decimal.precision()).unwrap_or(u8::MAX)).unwrap(),
226 target_scale,
227 )
228 .unwrap();
229
230 assert_eq!(limbs, TestScalar::from(expected_limbs));
231 }
232
233 #[test]
234 fn we_can_match_negative_decimals() {
235 let decimal = "-123.45".parse().unwrap();
236 let target_scale = 2;
237 let expected_limbs = [12345, 0, 0, 0];
238 let limbs = try_convert_intermediate_decimal_to_scalar::<TestScalar>(
239 &decimal,
240 Precision::new(u8::try_from(decimal.precision()).unwrap_or(u8::MAX)).unwrap(),
241 target_scale,
242 )
243 .unwrap();
244 assert_eq!(limbs, -TestScalar::from(expected_limbs));
245 }
246
247 #[allow(clippy::cast_possible_wrap)]
248 #[test]
249 fn we_can_match_decimals_at_extrema() {
250 let decimal = "1234567890123456789012345678901234567890123456789012345678901234567890.0"
252 .parse()
253 .unwrap();
254 let target_scale = 6; assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
256 &decimal,
257 Precision::new(u8::try_from(decimal.precision()).unwrap_or(u8::MAX),).unwrap(),
258 target_scale
259 )
260 .is_err());
261
262 let decimal =
264 "99999999999999999999999999999999999999999999999999999999999999999999999999.0"
265 .parse()
266 .unwrap();
267 let target_scale = 1;
268 assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
269 &decimal,
270 Precision::new(MAX_SUPPORTED_PRECISION).unwrap(),
271 target_scale
272 )
273 .is_ok());
274
275 let decimal =
277 "999999999999999999999999999999999999999999999999999999999999999999999999999.0"
278 .parse()
279 .unwrap();
280 let target_scale = 1;
281 assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
282 &decimal,
283 Precision::new(MAX_SUPPORTED_PRECISION).unwrap(),
284 target_scale
285 )
286 .is_err());
287
288 let decimal =
290 "0.000000000000000000000000000000000000000000000000000000000000000000000000001"
291 .parse()
292 .unwrap();
293 let target_scale = MAX_SUPPORTED_PRECISION as i8;
294 assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
295 &decimal,
296 Precision::new(u8::try_from(decimal.precision()).unwrap_or(u8::MAX),).unwrap(),
297 target_scale
298 )
299 .is_ok());
300
301 let decimal = "0.1".parse().unwrap();
303 let target_scale = MAX_SUPPORTED_PRECISION as i8;
304 assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
305 &decimal,
306 Precision::new(MAX_SUPPORTED_PRECISION).unwrap(),
307 target_scale
308 )
309 .is_ok());
310
311 let decimal = "1.0".parse().unwrap();
313 let target_scale = 75;
314 assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
315 &decimal,
316 Precision::new(u8::try_from(decimal.precision()).unwrap_or(u8::MAX),).unwrap(),
317 target_scale
318 )
319 .is_err());
320
321 let decimal = "1.0".parse().unwrap();
323 let target_scale = 74;
324 assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
325 &decimal,
326 Precision::new(MAX_SUPPORTED_PRECISION).unwrap(),
327 target_scale
328 )
329 .is_ok());
330 }
331}