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)]
90#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
92pub struct Precision(#[cfg_attr(test, proptest(strategy = "1..76u8"))] u8);
93pub(crate) const MAX_SUPPORTED_PRECISION: u8 = 75;
94
95impl Precision {
96 pub fn new(value: u8) -> Result<Self, DecimalError> {
98 if value > MAX_SUPPORTED_PRECISION || value == 0 {
99 Err(DecimalError::InvalidPrecision {
100 error: value.to_string(),
101 })
102 } else {
103 Ok(Precision(value))
104 }
105 }
106
107 #[must_use]
109 pub fn value(&self) -> u8 {
110 self.0
111 }
112}
113
114impl TryFrom<u64> for Precision {
115 type Error = DecimalError;
116 fn try_from(value: u64) -> Result<Self, Self::Error> {
117 Precision::new(
118 value
119 .try_into()
120 .map_err(|_| DecimalError::InvalidPrecision {
121 error: value.to_string(),
122 })?,
123 )
124 }
125}
126
127impl<'de> Deserialize<'de> for Precision {
129 fn deserialize<D>(deserializer: D) -> Result<Precision, D::Error>
130 where
131 D: Deserializer<'de>,
132 {
133 let value = u8::deserialize(deserializer)?;
135
136 Precision::new(value).map_err(serde::de::Error::custom)
138 }
139}
140
141pub(crate) fn try_convert_intermediate_decimal_to_scalar<S: Scalar>(
159 d: &BigDecimal,
160 target_precision: Precision,
161 target_scale: i8,
162) -> DecimalResult<S> {
163 d.try_into_bigint_with_precision_and_scale(target_precision.value(), target_scale)?
164 .try_into()
165 .map_err(|e: ScalarConversionError| DecimalError::InvalidDecimal {
166 error: e.to_string(),
167 })
168}
169
170#[cfg(test)]
171mod scale_adjust_test {
172
173 use super::*;
174 use crate::base::scalar::test_scalar::TestScalar;
175 use num_bigint::BigInt;
176
177 #[test]
178 fn we_cannot_scale_past_max_precision() {
179 let decimal = "12345678901234567890123456789012345678901234567890123456789012345678900.0"
180 .parse()
181 .unwrap();
182
183 let target_scale = 5;
184
185 assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
186 &decimal,
187 Precision::new(u8::try_from(decimal.precision()).unwrap_or(u8::MAX)).unwrap(),
188 target_scale
189 )
190 .is_err());
191 }
192
193 #[test]
194 fn we_can_match_exact_decimals_from_queries_to_db() {
195 let decimal: BigDecimal = "123.45".parse().unwrap();
196 let target_scale = 2;
197 let target_precision = 20;
198 let big_int =
199 decimal.try_into_bigint_with_precision_and_scale(target_precision, target_scale);
200 let expected_big_int: BigInt = "12345".parse().unwrap();
201 assert_eq!(big_int, Ok(expected_big_int));
202 }
203
204 #[test]
205 fn we_can_match_decimals_with_negative_scale() {
206 let decimal = "120.00".parse().unwrap();
207 let target_scale = -1;
208 let expected = [12, 0, 0, 0];
209 let result = try_convert_intermediate_decimal_to_scalar::<TestScalar>(
210 &decimal,
211 Precision::new(MAX_SUPPORTED_PRECISION).unwrap(),
212 target_scale,
213 )
214 .unwrap();
215 assert_eq!(result, TestScalar::from(expected));
216 }
217
218 #[test]
219 fn we_can_match_integers_with_negative_scale() {
220 let decimal = "12300".parse().unwrap();
221 let target_scale = -2;
222 let expected_limbs = [123, 0, 0, 0];
223
224 let limbs = try_convert_intermediate_decimal_to_scalar::<TestScalar>(
225 &decimal,
226 Precision::new(u8::try_from(decimal.precision()).unwrap_or(u8::MAX)).unwrap(),
227 target_scale,
228 )
229 .unwrap();
230
231 assert_eq!(limbs, TestScalar::from(expected_limbs));
232 }
233
234 #[test]
235 fn we_can_match_negative_decimals() {
236 let decimal = "-123.45".parse().unwrap();
237 let target_scale = 2;
238 let expected_limbs = [12345, 0, 0, 0];
239 let limbs = try_convert_intermediate_decimal_to_scalar::<TestScalar>(
240 &decimal,
241 Precision::new(u8::try_from(decimal.precision()).unwrap_or(u8::MAX)).unwrap(),
242 target_scale,
243 )
244 .unwrap();
245 assert_eq!(limbs, -TestScalar::from(expected_limbs));
246 }
247
248 #[expect(clippy::cast_possible_wrap)]
249 #[test]
250 fn we_can_match_decimals_at_extrema() {
251 let decimal = "1234567890123456789012345678901234567890123456789012345678901234567890.0"
253 .parse()
254 .unwrap();
255 let target_scale = 6; assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
257 &decimal,
258 Precision::new(u8::try_from(decimal.precision()).unwrap_or(u8::MAX),).unwrap(),
259 target_scale
260 )
261 .is_err());
262
263 let decimal =
265 "99999999999999999999999999999999999999999999999999999999999999999999999999.0"
266 .parse()
267 .unwrap();
268 let target_scale = 1;
269 assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
270 &decimal,
271 Precision::new(MAX_SUPPORTED_PRECISION).unwrap(),
272 target_scale
273 )
274 .is_ok());
275
276 let decimal =
278 "999999999999999999999999999999999999999999999999999999999999999999999999999.0"
279 .parse()
280 .unwrap();
281 let target_scale = 1;
282 assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
283 &decimal,
284 Precision::new(MAX_SUPPORTED_PRECISION).unwrap(),
285 target_scale
286 )
287 .is_err());
288
289 let decimal =
291 "0.000000000000000000000000000000000000000000000000000000000000000000000000001"
292 .parse()
293 .unwrap();
294 let target_scale = MAX_SUPPORTED_PRECISION as i8;
295 assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
296 &decimal,
297 Precision::new(u8::try_from(decimal.precision()).unwrap_or(u8::MAX),).unwrap(),
298 target_scale
299 )
300 .is_ok());
301
302 let decimal = "0.1".parse().unwrap();
304 let target_scale = MAX_SUPPORTED_PRECISION as i8;
305 assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
306 &decimal,
307 Precision::new(MAX_SUPPORTED_PRECISION).unwrap(),
308 target_scale
309 )
310 .is_ok());
311
312 let decimal = "1.0".parse().unwrap();
314 let target_scale = 75;
315 assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
316 &decimal,
317 Precision::new(u8::try_from(decimal.precision()).unwrap_or(u8::MAX),).unwrap(),
318 target_scale
319 )
320 .is_err());
321
322 let decimal = "1.0".parse().unwrap();
324 let target_scale = 74;
325 assert!(try_convert_intermediate_decimal_to_scalar::<TestScalar>(
326 &decimal,
327 Precision::new(MAX_SUPPORTED_PRECISION).unwrap(),
328 target_scale
329 )
330 .is_ok());
331 }
332}