icydb_schema/decimal/
mod.rs1mod arithmetic;
6mod compare;
7mod text;
8mod wire;
9
10#[cfg(test)]
11mod tests;
12
13use crate::NumericValue;
14use std::fmt::{Display, Formatter};
15use std::str::FromStr;
16
17pub(crate) const MAX_SUPPORTED_SCALE: u32 = 28;
20pub(crate) const DEFAULT_DIVISION_SCALE: u32 = 18;
21pub(crate) const DECIMAL_DIGIT_BUFFER_LEN: usize = 39;
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub struct DecimalParts {
34 mantissa: i128,
35 scale: u32,
36}
37
38impl DecimalParts {
39 #[must_use]
41 pub const fn mantissa(&self) -> i128 {
42 self.mantissa
43 }
44
45 #[must_use]
47 pub const fn scale(&self) -> u32 {
48 self.scale
49 }
50}
51
52#[derive(Clone, Copy, Debug, Eq, PartialEq)]
62pub struct ParseDecimalError {
63 reason: ParseDecimalErrorReason,
64}
65
66impl ParseDecimalError {
67 pub(crate) const fn new(reason: ParseDecimalErrorReason) -> Self {
68 Self { reason }
69 }
70
71 #[must_use]
73 pub const fn reason(&self) -> ParseDecimalErrorReason {
74 self.reason
75 }
76}
77
78impl std::error::Error for ParseDecimalError {}
79
80impl Display for ParseDecimalError {
81 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
82 f.write_str("decimal parse error")
83 }
84}
85
86#[derive(Clone, Copy, Debug, Eq, PartialEq)]
93#[repr(u8)]
94pub enum ParseDecimalErrorReason {
95 Empty,
96 ExponentNotationUnsupported,
97 FractionalLengthOverflow,
98 ScaleOverflow,
99 MantissaOverflow,
100 ScaleExceedsSupportedRange,
101 InvalidSignificand,
102 InvalidDigits,
103}
104
105#[derive(Clone, Copy, Debug, Default)]
116pub struct Decimal {
117 mantissa: i128,
118 scale: u32,
119}
120
121impl Decimal {
122 pub const ZERO: Self = Self {
123 mantissa: 0,
124 scale: 0,
125 };
126
127 #[must_use]
129 pub const fn max_supported_scale() -> u32 {
130 MAX_SUPPORTED_SCALE
131 }
132
133 #[must_use]
139 pub const fn new(num: i64, scale: u32) -> Self {
140 assert!(
141 scale <= MAX_SUPPORTED_SCALE,
142 "decimal scale exceeds supported range"
143 );
144 Self::new_unchecked(num, scale)
145 }
146
147 #[must_use]
149 pub const fn try_new(num: i64, scale: u32) -> Option<Self> {
150 if scale > MAX_SUPPORTED_SCALE {
151 return None;
152 }
153
154 Some(Self::new_unchecked(num, scale))
155 }
156
157 #[must_use]
168 pub(crate) const fn new_unchecked(num: i64, scale: u32) -> Self {
169 Self {
170 mantissa: num as i128,
171 scale,
172 }
173 }
174
175 pub fn from_num<N: NumericValue>(n: N) -> Option<Self> {
181 n.try_to_decimal()
182 }
183
184 #[must_use]
186 pub const fn from_i64(n: i64) -> Option<Self> {
187 Some(Self {
188 mantissa: n as i128,
189 scale: 0,
190 })
191 }
192
193 #[must_use]
195 pub const fn from_u64(n: u64) -> Option<Self> {
196 Some(Self {
197 mantissa: n as i128,
198 scale: 0,
199 })
200 }
201
202 #[must_use]
204 pub const fn from_i128(n: i128) -> Option<Self> {
205 Some(Self {
206 mantissa: n,
207 scale: 0,
208 })
209 }
210
211 #[must_use]
213 pub fn from_u128(n: u128) -> Option<Self> {
214 Some(Self {
215 mantissa: i128::try_from(n).ok()?,
216 scale: 0,
217 })
218 }
219
220 #[must_use]
225 pub fn from_f32_lossy(n: f32) -> Option<Self> {
226 if !n.is_finite() {
227 return None;
228 }
229
230 Self::from_str(&n.to_string()).ok()
231 }
232
233 #[must_use]
238 pub fn from_f64_lossy(n: f64) -> Option<Self> {
239 if !n.is_finite() {
240 return None;
241 }
242
243 Self::from_str(&n.to_string()).ok()
244 }
245
246 #[must_use]
252 pub const fn parts(&self) -> DecimalParts {
253 DecimalParts {
254 mantissa: self.mantissa,
255 scale: self.scale,
256 }
257 }
258
259 #[must_use]
261 pub const fn is_integer(&self) -> bool {
262 self.scale == 0
263 }
264
265 #[must_use]
271 pub fn scale_to_integer(&self, target_scale: u32) -> Option<i128> {
272 if self.scale > target_scale {
273 return None;
274 }
275
276 let factor = Self::checked_pow10(target_scale - self.scale)?;
277 self.mantissa.checked_mul(factor)
278 }
279
280 #[must_use]
282 pub fn to_i32(&self) -> Option<i32> {
283 self.to_i64().and_then(|value| i32::try_from(value).ok())
284 }
285
286 #[must_use]
288 pub fn to_i64(&self) -> Option<i64> {
289 let integer = Self::decimal_integer_value(self.mantissa, self.scale)?;
290
291 i64::try_from(integer).ok()
292 }
293
294 #[must_use]
296 pub fn to_i128(&self) -> Option<i128> {
297 Self::decimal_integer_value(self.mantissa, self.scale)
298 }
299
300 #[must_use]
302 pub fn to_u64(&self) -> Option<u64> {
303 let integer = Self::decimal_integer_value(self.mantissa, self.scale)?;
304
305 u64::try_from(integer).ok()
306 }
307
308 #[must_use]
310 pub fn to_u128(&self) -> Option<u128> {
311 let integer = Self::decimal_integer_value(self.mantissa, self.scale)?;
312
313 u128::try_from(integer).ok()
314 }
315
316 #[must_use]
318 #[expect(clippy::cast_possible_truncation)]
319 pub fn to_f32(&self) -> Option<f32> {
320 self.to_f64().and_then(|value| {
321 let float = value as f32;
322 if float.is_finite() { Some(float) } else { None }
323 })
324 }
325
326 #[must_use]
328 #[expect(clippy::cast_precision_loss)]
329 pub fn to_f64(&self) -> Option<f64> {
330 let divisor = 10f64.powi(i32::try_from(self.scale).ok()?);
331 let value = (self.mantissa as f64) / divisor;
332
333 if value.is_finite() { Some(value) } else { None }
334 }
335
336 #[must_use]
338 pub const fn try_from_i128_with_scale(num: i128, scale: u32) -> Option<Self> {
339 Self::checked_from_mantissa_scale(num, scale)
340 }
341
342 #[must_use]
349 pub const fn from_i128_with_scale(num: i128, scale: u32) -> Self {
350 Self::try_from_i128_with_scale(num, scale).expect("decimal invariant")
351 }
352
353 #[must_use]
355 pub const fn normalize(&self) -> Self {
356 let (mantissa, scale) = self.normalized_parts();
357 Self { mantissa, scale }
358 }
359
360 #[must_use]
362 pub const fn is_sign_negative(&self) -> bool {
363 self.mantissa < 0
364 }
365
366 #[must_use]
368 pub const fn scale(&self) -> u32 {
369 self.scale
370 }
371
372 #[must_use]
374 pub const fn mantissa(&self) -> i128 {
375 self.mantissa
376 }
377
378 #[must_use]
380 pub const fn is_zero(&self) -> bool {
381 self.mantissa == 0
382 }
383
384 const fn normalized_parts(&self) -> (i128, u32) {
385 Self::normalize_parts(self.mantissa, self.scale)
386 }
387
388 const fn checked_from_mantissa_scale(mantissa: i128, scale: u32) -> Option<Self> {
389 if scale <= MAX_SUPPORTED_SCALE {
390 return Some(Self { mantissa, scale });
391 }
392
393 let mut m = mantissa;
394 let mut s = scale;
395
396 while s > MAX_SUPPORTED_SCALE {
397 if m == 0 {
398 return Some(Self {
399 mantissa: 0,
400 scale: MAX_SUPPORTED_SCALE,
401 });
402 }
403
404 if m % 10 != 0 {
405 return None;
406 }
407
408 m /= 10;
409 s -= 1;
410 }
411
412 Some(Self {
413 mantissa: m,
414 scale: s,
415 })
416 }
417
418 const fn checked_pow10(power: u32) -> Option<i128> {
419 10i128.checked_pow(power)
420 }
421
422 fn decimal_integer_value(mantissa: i128, scale: u32) -> Option<i128> {
423 if scale == 0 {
424 return Some(mantissa);
425 }
426
427 let divisor = Self::checked_pow10(scale)?;
428 if mantissa % divisor != 0 {
429 return None;
430 }
431
432 Some(mantissa / divisor)
433 }
434
435 const fn normalize_parts(mantissa: i128, scale: u32) -> (i128, u32) {
436 if mantissa == 0 {
437 return (0, 0);
438 }
439
440 let mut m = mantissa;
441 let mut s = scale;
442
443 while s > 0 {
444 if m % 10 != 0 {
445 break;
446 }
447
448 m /= 10;
449 s -= 1;
450 }
451
452 (m, s)
453 }
454
455 const fn saturating_extreme(scale: u32, negative: bool) -> Self {
456 let mantissa = if negative { i128::MIN } else { i128::MAX };
457 Self { mantissa, scale }
458 }
459}
460
461impl NumericValue for Decimal {
462 fn try_to_decimal(&self) -> Option<Self> {
463 Some(*self)
464 }
465
466 fn try_from_decimal(value: Decimal) -> Option<Self> {
467 Some(value)
468 }
469}