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)]
115pub struct Decimal {
116 mantissa: i128,
117 scale: u32,
118}
119
120impl Decimal {
121 pub const ZERO: Self = Self {
122 mantissa: 0,
123 scale: 0,
124 };
125
126 #[must_use]
128 pub const fn max_supported_scale() -> u32 {
129 MAX_SUPPORTED_SCALE
130 }
131
132 #[must_use]
138 pub const fn new(num: i64, scale: u32) -> Self {
139 assert!(
140 scale <= MAX_SUPPORTED_SCALE,
141 "decimal scale exceeds supported range"
142 );
143 Self::new_unchecked(num, scale)
144 }
145
146 #[must_use]
148 pub const fn try_new(num: i64, scale: u32) -> Option<Self> {
149 if scale > MAX_SUPPORTED_SCALE {
150 return None;
151 }
152
153 Some(Self::new_unchecked(num, scale))
154 }
155
156 #[must_use]
167 pub(crate) const fn new_unchecked(num: i64, scale: u32) -> Self {
168 Self {
169 mantissa: num as i128,
170 scale,
171 }
172 }
173
174 pub fn from_num<N: NumericValue>(n: N) -> Option<Self> {
180 n.try_to_decimal()
181 }
182
183 #[must_use]
185 pub const fn from_i64(n: i64) -> Option<Self> {
186 Some(Self {
187 mantissa: n as i128,
188 scale: 0,
189 })
190 }
191
192 #[must_use]
194 pub const fn from_u64(n: u64) -> Option<Self> {
195 Some(Self {
196 mantissa: n as i128,
197 scale: 0,
198 })
199 }
200
201 #[must_use]
203 pub const fn from_i128(n: i128) -> Option<Self> {
204 Some(Self {
205 mantissa: n,
206 scale: 0,
207 })
208 }
209
210 #[must_use]
212 pub fn from_u128(n: u128) -> Option<Self> {
213 Some(Self {
214 mantissa: i128::try_from(n).ok()?,
215 scale: 0,
216 })
217 }
218
219 #[must_use]
224 pub fn from_f32_lossy(n: f32) -> Option<Self> {
225 if !n.is_finite() {
226 return None;
227 }
228
229 Self::from_str(&n.to_string()).ok()
230 }
231
232 #[must_use]
237 pub fn from_f64_lossy(n: f64) -> Option<Self> {
238 if !n.is_finite() {
239 return None;
240 }
241
242 Self::from_str(&n.to_string()).ok()
243 }
244
245 #[must_use]
251 pub const fn parts(&self) -> DecimalParts {
252 DecimalParts {
253 mantissa: self.mantissa,
254 scale: self.scale,
255 }
256 }
257
258 #[must_use]
260 pub const fn is_integer(&self) -> bool {
261 self.scale == 0
262 }
263
264 #[must_use]
270 pub fn scale_to_integer(&self, target_scale: u32) -> Option<i128> {
271 if self.scale > target_scale {
272 return None;
273 }
274
275 let factor = Self::checked_pow10(target_scale - self.scale)?;
276 self.mantissa.checked_mul(factor)
277 }
278
279 #[must_use]
281 pub fn to_i32(&self) -> Option<i32> {
282 self.to_i64().and_then(|value| i32::try_from(value).ok())
283 }
284
285 #[must_use]
287 pub fn to_i64(&self) -> Option<i64> {
288 let integer = Self::decimal_integer_value(self.mantissa, self.scale)?;
289
290 i64::try_from(integer).ok()
291 }
292
293 #[must_use]
295 pub fn to_i128(&self) -> Option<i128> {
296 Self::decimal_integer_value(self.mantissa, self.scale)
297 }
298
299 #[must_use]
301 pub fn to_u64(&self) -> Option<u64> {
302 let integer = Self::decimal_integer_value(self.mantissa, self.scale)?;
303
304 u64::try_from(integer).ok()
305 }
306
307 #[must_use]
309 pub fn to_u128(&self) -> Option<u128> {
310 let integer = Self::decimal_integer_value(self.mantissa, self.scale)?;
311
312 u128::try_from(integer).ok()
313 }
314
315 #[must_use]
317 #[expect(clippy::cast_possible_truncation)]
318 pub fn to_f32(&self) -> Option<f32> {
319 self.to_f64().and_then(|value| {
320 let float = value as f32;
321 if float.is_finite() { Some(float) } else { None }
322 })
323 }
324
325 #[must_use]
327 #[expect(clippy::cast_precision_loss)]
328 pub fn to_f64(&self) -> Option<f64> {
329 let divisor = 10f64.powi(i32::try_from(self.scale).ok()?);
330 let value = (self.mantissa as f64) / divisor;
331
332 if value.is_finite() { Some(value) } else { None }
333 }
334
335 #[must_use]
337 pub const fn try_from_i128_with_scale(num: i128, scale: u32) -> Option<Self> {
338 Self::checked_from_mantissa_scale(num, scale)
339 }
340
341 #[must_use]
348 pub const fn from_i128_with_scale(num: i128, scale: u32) -> Self {
349 Self::try_from_i128_with_scale(num, scale).expect("decimal invariant")
350 }
351
352 #[must_use]
354 pub const fn normalize(&self) -> Self {
355 let (mantissa, scale) = self.normalized_parts();
356 Self { mantissa, scale }
357 }
358
359 #[must_use]
361 pub const fn is_sign_negative(&self) -> bool {
362 self.mantissa < 0
363 }
364
365 #[must_use]
367 pub const fn scale(&self) -> u32 {
368 self.scale
369 }
370
371 #[must_use]
373 pub const fn mantissa(&self) -> i128 {
374 self.mantissa
375 }
376
377 #[must_use]
379 pub const fn is_zero(&self) -> bool {
380 self.mantissa == 0
381 }
382
383 const fn normalized_parts(&self) -> (i128, u32) {
384 Self::normalize_parts(self.mantissa, self.scale)
385 }
386
387 const fn checked_from_mantissa_scale(mantissa: i128, scale: u32) -> Option<Self> {
388 if scale <= MAX_SUPPORTED_SCALE {
389 return Some(Self { mantissa, scale });
390 }
391
392 let mut m = mantissa;
393 let mut s = scale;
394
395 while s > MAX_SUPPORTED_SCALE {
396 if m == 0 {
397 return Some(Self {
398 mantissa: 0,
399 scale: MAX_SUPPORTED_SCALE,
400 });
401 }
402
403 if m % 10 != 0 {
404 return None;
405 }
406
407 m /= 10;
408 s -= 1;
409 }
410
411 Some(Self {
412 mantissa: m,
413 scale: s,
414 })
415 }
416
417 const fn checked_pow10(power: u32) -> Option<i128> {
418 10i128.checked_pow(power)
419 }
420
421 fn decimal_integer_value(mantissa: i128, scale: u32) -> Option<i128> {
422 if scale == 0 {
423 return Some(mantissa);
424 }
425
426 let divisor = Self::checked_pow10(scale)?;
427 if mantissa % divisor != 0 {
428 return None;
429 }
430
431 Some(mantissa / divisor)
432 }
433
434 const fn normalize_parts(mantissa: i128, scale: u32) -> (i128, u32) {
435 if mantissa == 0 {
436 return (0, 0);
437 }
438
439 let mut m = mantissa;
440 let mut s = scale;
441
442 while s > 0 {
443 if m % 10 != 0 {
444 break;
445 }
446
447 m /= 10;
448 s -= 1;
449 }
450
451 (m, s)
452 }
453
454 const fn saturating_extreme(scale: u32, negative: bool) -> Self {
455 let mantissa = if negative { i128::MIN } else { i128::MAX };
456 Self { mantissa, scale }
457 }
458}
459
460impl NumericValue for Decimal {
461 fn try_to_decimal(&self) -> Option<Self> {
462 Some(*self)
463 }
464
465 fn try_from_decimal(value: Decimal) -> Option<Self> {
466 Some(value)
467 }
468}