icydb_schema/decimal/
mod.rs1mod arithmetic;
6mod compare;
7mod text;
8mod wire;
9
10#[cfg(test)]
11mod tests;
12
13use crate::NumericValue;
14
15pub(crate) const MAX_SUPPORTED_SCALE: u32 = 28;
18pub(crate) const DEFAULT_DIVISION_SCALE: u32 = 18;
19pub(crate) const DECIMAL_DIGIT_BUFFER_LEN: usize = 39;
20
21#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub struct DecimalParts {
32 mantissa: i128,
33 scale: u32,
34}
35
36impl DecimalParts {
37 #[must_use]
39 pub const fn mantissa(&self) -> i128 {
40 self.mantissa
41 }
42
43 #[must_use]
45 pub const fn scale(&self) -> u32 {
46 self.scale
47 }
48}
49
50#[derive(Clone, Copy, Debug, Eq, thiserror::Error, PartialEq)]
60#[error("decimal parse error")]
61pub struct ParseDecimalError {
62 reason: ParseDecimalErrorReason,
63}
64
65impl ParseDecimalError {
66 pub(crate) const fn new(reason: ParseDecimalErrorReason) -> Self {
67 Self { reason }
68 }
69
70 #[must_use]
72 pub const fn reason(&self) -> ParseDecimalErrorReason {
73 self.reason
74 }
75}
76
77#[derive(Clone, Copy, Debug, Eq, PartialEq)]
84#[repr(u8)]
85pub enum ParseDecimalErrorReason {
86 Empty,
87 ExponentNotationUnsupported,
88 FractionalLengthOverflow,
89 ScaleOverflow,
90 MantissaOverflow,
91 ScaleExceedsSupportedRange,
92 InvalidSignificand,
93 InvalidDigits,
94}
95
96#[derive(Clone, Copy, Debug, Default)]
108pub struct Decimal {
109 mantissa: i128,
110 scale: u32,
111}
112
113impl Decimal {
114 pub const ZERO: Self = Self {
115 mantissa: 0,
116 scale: 0,
117 };
118
119 #[must_use]
121 pub const fn max_supported_scale() -> u32 {
122 MAX_SUPPORTED_SCALE
123 }
124
125 #[must_use]
131 pub const fn new(num: i64, scale: u32) -> Self {
132 assert!(
133 scale <= MAX_SUPPORTED_SCALE,
134 "decimal scale exceeds supported range"
135 );
136 Self::new_unchecked(num, scale)
137 }
138
139 #[must_use]
141 pub const fn try_new(num: i64, scale: u32) -> Option<Self> {
142 if scale > MAX_SUPPORTED_SCALE {
143 return None;
144 }
145
146 Some(Self::new_unchecked(num, scale))
147 }
148
149 #[must_use]
160 pub(crate) const fn new_unchecked(num: i64, scale: u32) -> Self {
161 Self {
162 mantissa: num as i128,
163 scale,
164 }
165 }
166
167 pub fn from_num<N: NumericValue>(n: N) -> Option<Self> {
173 n.try_to_decimal()
174 }
175
176 #[must_use]
178 pub const fn from_i64(n: i64) -> Option<Self> {
179 Some(Self {
180 mantissa: n as i128,
181 scale: 0,
182 })
183 }
184
185 #[must_use]
187 pub const fn from_u64(n: u64) -> Option<Self> {
188 Some(Self {
189 mantissa: n as i128,
190 scale: 0,
191 })
192 }
193
194 #[must_use]
196 pub const fn from_i128(n: i128) -> Option<Self> {
197 Some(Self {
198 mantissa: n,
199 scale: 0,
200 })
201 }
202
203 #[must_use]
205 pub fn from_u128(n: u128) -> Option<Self> {
206 Some(Self {
207 mantissa: i128::try_from(n).ok()?,
208 scale: 0,
209 })
210 }
211
212 #[must_use]
217 pub fn from_f32_lossy(n: f32) -> Option<Self> {
218 if !n.is_finite() {
219 return None;
220 }
221
222 Self::from_float_text(format_args!("{n}"))
223 }
224
225 #[must_use]
230 pub fn from_f64_lossy(n: f64) -> Option<Self> {
231 if !n.is_finite() {
232 return None;
233 }
234
235 Self::from_float_text(format_args!("{n}"))
236 }
237
238 #[must_use]
244 pub const fn parts(&self) -> DecimalParts {
245 DecimalParts {
246 mantissa: self.mantissa,
247 scale: self.scale,
248 }
249 }
250
251 #[must_use]
253 pub const fn is_integer(&self) -> bool {
254 self.scale == 0
255 }
256
257 #[must_use]
263 pub fn scale_to_integer(&self, target_scale: u32) -> Option<i128> {
264 if self.scale > target_scale {
265 return None;
266 }
267
268 let factor = Self::checked_pow10(target_scale - self.scale)?;
269 self.mantissa.checked_mul(factor)
270 }
271
272 #[must_use]
274 pub fn to_i32(&self) -> Option<i32> {
275 self.to_i64().and_then(|value| i32::try_from(value).ok())
276 }
277
278 #[must_use]
280 pub fn to_i64(&self) -> Option<i64> {
281 let integer = Self::decimal_integer_value(self.mantissa, self.scale)?;
282
283 i64::try_from(integer).ok()
284 }
285
286 #[must_use]
288 pub fn to_i128(&self) -> Option<i128> {
289 Self::decimal_integer_value(self.mantissa, self.scale)
290 }
291
292 #[must_use]
294 pub fn to_u64(&self) -> Option<u64> {
295 let integer = Self::decimal_integer_value(self.mantissa, self.scale)?;
296
297 u64::try_from(integer).ok()
298 }
299
300 #[must_use]
302 pub fn to_u128(&self) -> Option<u128> {
303 let integer = Self::decimal_integer_value(self.mantissa, self.scale)?;
304
305 u128::try_from(integer).ok()
306 }
307
308 #[must_use]
310 #[expect(clippy::cast_possible_truncation)]
311 pub fn to_f32(&self) -> Option<f32> {
312 self.to_f64().and_then(|value| {
313 let float = value as f32;
314 if float.is_finite() { Some(float) } else { None }
315 })
316 }
317
318 #[must_use]
320 #[expect(clippy::cast_precision_loss)]
321 pub fn to_f64(&self) -> Option<f64> {
322 let divisor = 10f64.powi(i32::try_from(self.scale).ok()?);
323 let value = (self.mantissa as f64) / divisor;
324
325 if value.is_finite() { Some(value) } else { None }
326 }
327
328 #[must_use]
330 pub const fn try_from_i128_with_scale(num: i128, scale: u32) -> Option<Self> {
331 Self::checked_from_mantissa_scale(num, scale)
332 }
333
334 #[must_use]
341 pub const fn from_i128_with_scale(num: i128, scale: u32) -> Self {
342 Self::try_from_i128_with_scale(num, scale).expect("decimal invariant")
343 }
344
345 #[must_use]
347 pub const fn normalize(&self) -> Self {
348 let (mantissa, scale) = self.normalized_parts();
349 Self { mantissa, scale }
350 }
351
352 #[must_use]
354 pub const fn is_sign_negative(&self) -> bool {
355 self.mantissa < 0
356 }
357
358 #[must_use]
360 pub const fn scale(&self) -> u32 {
361 self.scale
362 }
363
364 #[must_use]
366 pub const fn mantissa(&self) -> i128 {
367 self.mantissa
368 }
369
370 #[must_use]
372 pub const fn is_zero(&self) -> bool {
373 self.mantissa == 0
374 }
375
376 const fn normalized_parts(&self) -> (i128, u32) {
377 Self::normalize_parts(self.mantissa, self.scale)
378 }
379
380 const fn checked_from_mantissa_scale(mantissa: i128, scale: u32) -> Option<Self> {
381 if scale <= MAX_SUPPORTED_SCALE {
382 return Some(Self { mantissa, scale });
383 }
384
385 let mut m = mantissa;
386 let mut s = scale;
387
388 while s > MAX_SUPPORTED_SCALE {
389 if m == 0 {
390 return Some(Self {
391 mantissa: 0,
392 scale: MAX_SUPPORTED_SCALE,
393 });
394 }
395
396 if m % 10 != 0 {
397 return None;
398 }
399
400 m /= 10;
401 s -= 1;
402 }
403
404 Some(Self {
405 mantissa: m,
406 scale: s,
407 })
408 }
409
410 const fn checked_pow10(power: u32) -> Option<i128> {
411 10i128.checked_pow(power)
412 }
413
414 fn decimal_integer_value(mantissa: i128, scale: u32) -> Option<i128> {
415 if scale == 0 {
416 return Some(mantissa);
417 }
418
419 let divisor = Self::checked_pow10(scale)?;
420 if mantissa % divisor != 0 {
421 return None;
422 }
423
424 Some(mantissa / divisor)
425 }
426
427 const fn normalize_parts(mantissa: i128, scale: u32) -> (i128, u32) {
428 if mantissa == 0 {
429 return (0, 0);
430 }
431
432 let mut m = mantissa;
433 let mut s = scale;
434
435 while s > 0 {
436 if m % 10 != 0 {
437 break;
438 }
439
440 m /= 10;
441 s -= 1;
442 }
443
444 (m, s)
445 }
446
447 const fn saturating_extreme(negative: bool) -> Self {
448 let mantissa = if negative { i128::MIN } else { i128::MAX };
449 Self { mantissa, scale: 0 }
450 }
451}
452
453impl NumericValue for Decimal {
454 fn try_to_decimal(&self) -> Option<Self> {
455 Some(*self)
456 }
457
458 fn try_from_decimal(value: Decimal) -> Option<Self> {
459 Some(value)
460 }
461}