1#[cfg(feature = "schemars")]
2use alloc::string::ToString;
3use alloc::{
4 fmt::{Debug, Display},
5 format,
6 str::FromStr,
7 string::String,
8 vec::Vec,
9};
10#[cfg(feature = "schemars")]
11use core::fmt::Write as _;
12use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
13
14use primitive_types::U512;
15
16pub const FRACTIONAL_BITS: usize = 128;
17pub const FRACTIONAL_DECIMAL_DIGITS: usize = 38;
19pub const WHOLE_DECIMAL_DIGITS: usize = 115;
21
22const MAX_WHOLE: U512 = U512([
23 u64::MAX,
24 u64::MAX,
25 u64::MAX,
26 u64::MAX,
27 u64::MAX,
28 u64::MAX,
29 0,
30 0,
31]);
32
33#[cfg(feature = "schemars")]
34fn decimal_whole_pattern(include_maximum: bool) -> String {
35 let maximum = MAX_WHOLE.to_string();
36 let bytes = maximum.as_bytes();
37 let mut branches = Vec::with_capacity(bytes.len() + 2);
38 branches.push("0".to_string());
39 branches.push("[1-9][0-9]{0,114}".to_string());
40
41 for (index, &digit) in bytes.iter().enumerate() {
42 let minimum = if index == 0 { b'1' } else { b'0' };
43 if digit > minimum {
44 let mut branch = maximum[..index].to_string();
45 if digit == minimum + 1 {
46 branch.push(char::from(minimum));
47 } else {
48 branch.push('[');
49 branch.push(char::from(minimum));
50 branch.push('-');
51 branch.push(char::from(digit - 1));
52 branch.push(']');
53 }
54 let remaining = bytes.len() - index - 1;
55 match remaining {
56 0 => {}
57 1 => branch.push_str("[0-9]"),
58 _ => write!(&mut branch, "[0-9]{{{remaining}}}").unwrap(),
59 }
60 branches.push(branch);
61 }
62 }
63 if include_maximum {
64 branches.push(maximum);
65 }
66 branches.join("|")
67}
68
69fn u512_pow10(mut exponent: u32) -> U512 {
71 let mut y = U512::one();
72 let mut x = U512::from(10);
73
74 while exponent > 1 {
75 if exponent % 2 == 1 {
76 y *= x;
77 }
78 x *= x;
79 exponent >>= 1;
80 }
81
82 x * y
83}
84
85#[macro_export]
86macro_rules! dec {
87 ($s:literal) => {
88 <$crate::number::Decimal as core::str::FromStr>::from_str($s).unwrap()
89 };
90}
91
92#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
93pub struct Decimal {
94 repr: U512,
95}
96
97impl Default for Decimal {
98 fn default() -> Self {
99 Self::ZERO
100 }
101}
102
103#[cfg(feature = "schemars")]
104impl schemars::JsonSchema for Decimal {
105 fn schema_name() -> String {
106 "Decimal".to_string()
107 }
108
109 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
110 let mut schema = gen.subschema_for::<String>().into_object();
111 schema.metadata().description = Some("512-bit fixed-precision decimal".to_string());
112 schema.string().pattern = Some(format!(
113 "^(?:{})(\\.[0-9]{{1,38}})?$",
114 decimal_whole_pattern(true),
115 ));
116 schema.into()
117 }
118}
119
120#[cfg(feature = "borsh")]
121impl borsh::BorshSchema for Decimal {
122 fn add_definitions_recursively(
123 definitions: &mut alloc::collections::BTreeMap<
124 borsh::schema::Declaration,
125 borsh::schema::Definition,
126 >,
127 ) {
128 <[u64; 8] as borsh::BorshSchema>::add_definitions_recursively(definitions);
129 }
130
131 fn declaration() -> borsh::schema::Declaration {
132 String::from("Decimal")
133 }
134}
135
136#[cfg(feature = "borsh")]
137impl borsh::BorshSerialize for Decimal {
138 fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
139 borsh::BorshSerialize::serialize(&self.repr.0, writer)
140 }
141}
142
143#[cfg(feature = "borsh")]
144impl borsh::BorshDeserialize for Decimal {
145 fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
146 Ok(Self {
147 repr: U512(borsh::BorshDeserialize::deserialize_reader(reader)?),
148 })
149 }
150}
151
152#[cfg(feature = "serde")]
153impl serde::Serialize for Decimal {
154 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
155 where
156 S: serde::Serializer,
157 {
158 serializer.serialize_str(&self.to_fixed(FRACTIONAL_DECIMAL_DIGITS))
159 }
160}
161
162#[cfg(feature = "serde")]
163impl<'de> serde::Deserialize<'de> for Decimal {
164 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
165 where
166 D: serde::Deserializer<'de>,
167 {
168 let s = <String as serde::Deserialize>::deserialize(deserializer)?;
169 Decimal::from_str(&s).map_err(serde::de::Error::custom)
170 }
171}
172
173impl Decimal {
174 const REPR_EPSILON: U512 = U512([0b1000, 0, 0, 0, 0, 0, 0, 0]);
177
178 pub const MAX: Self = Self { repr: U512::MAX };
179 pub const MIN: Self = Self { repr: U512::zero() };
180
181 pub const ZERO: Self = Self { repr: U512::zero() };
182 pub const ONE_HALF: Self = Self {
183 repr: U512([0, 0x8000_0000_0000_0000, 0, 0, 0, 0, 0, 0]),
184 };
185 #[rustfmt::skip]
186 pub const LN2: Self = Self {
187 repr: U512([0xC9E3_B398_03F2_F6B0, 0xB172_17F7_D1CF_79AB, 0, 0, 0, 0, 0, 0]),
188 };
189 pub const ONE: Self = Self {
190 repr: U512([0, 0, 1, 0, 0, 0, 0, 0]),
191 };
192 pub const TWO: Self = Self {
193 repr: U512([0, 0, 2, 0, 0, 0, 0, 0]),
194 };
195 #[rustfmt::skip]
196 pub const E: Self = Self {
197 repr: U512([0xBF71_5880_9CF4_F3C9, 0xB7E1_5162_8AED_2A6A, 2, 0, 0, 0, 0, 0]),
198 };
199
200 pub fn as_repr(self) -> [u64; 8] {
201 self.repr.0
202 }
203
204 pub const fn from_repr(repr: [u64; 8]) -> Self {
205 Self { repr: U512(repr) }
206 }
207
208 pub fn is_zero(&self) -> bool {
209 self.repr.is_zero()
210 }
211
212 pub fn near_equal(self, other: Self) -> bool {
213 self.abs_diff(other).repr <= Self::REPR_EPSILON
214 }
215
216 #[must_use]
217 pub fn pow(self, mut exponent: i32) -> Self {
218 if exponent == 0 {
219 return Self::ONE;
220 }
221
222 let exponent_is_negative = if exponent < 0 {
223 exponent = -exponent;
224 true
225 } else {
226 false
227 };
228
229 let mut y = Self::ONE;
230 let mut x = self;
231
232 while exponent > 1 {
233 if exponent % 2 == 1 {
234 y *= x;
235 }
236 x *= x;
237 exponent >>= 1;
238 }
239
240 let result = x * y;
241
242 if exponent_is_negative {
243 Decimal::ONE / result
244 } else {
245 result
246 }
247 }
248
249 pub fn pow2_int(exponent: u32) -> Option<Self> {
251 #[allow(clippy::cast_possible_truncation)]
252 if exponent > 512 - FRACTIONAL_BITS as u32 {
253 None
254 } else {
255 Some(Self {
256 repr: Self::ONE.repr << exponent,
257 })
258 }
259 }
260
261 fn pow2_frac(self) -> Self {
262 const MAX_ITERATIONS: u32 = 35; debug_assert!(self <= Self::ONE);
264
265 let mut sum = Self::ONE;
266 let mut term = Self::ONE;
267 let numerator = self * Self::LN2;
268
269 for n in 1..=MAX_ITERATIONS {
270 term *= numerator / n;
271 if term == Self::ZERO {
272 break;
273 }
274 sum += &term;
275 }
276
277 sum
278 }
279
280 pub fn pow2(self) -> Option<Self> {
281 let whole = u32::try_from(self.to_u128_floor()?).ok()?;
282 let frac = self - whole;
283
284 Some(Self::pow2_int(whole)? * Self::pow2_frac(frac))
285 }
286
287 #[must_use]
288 pub fn mul_pow10(self, exponent: i32) -> Option<Self> {
289 if exponent == 0 || self.is_zero() {
290 return Some(self);
291 }
292
293 let abs_exponent = exponent.abs_diff(0);
294 if (abs_exponent as usize) > WHOLE_DECIMAL_DIGITS + FRACTIONAL_DECIMAL_DIGITS {
295 return None;
296 }
297
298 if exponent >= 0 {
299 let operand = u512_pow10(abs_exponent);
300 let (repr, overflowed) = self.repr.overflowing_mul(operand);
301 if !overflowed {
302 return Some(Self { repr });
303 }
304 } else {
305 let operand = u512_pow10(abs_exponent);
306 let repr = self.repr / operand;
307 if !repr.is_zero() {
308 return Some(Self { repr });
309 }
310 }
311
312 None
313 }
314
315 #[must_use]
316 pub fn abs_diff(self, other: Self) -> Self {
317 if self > other {
318 self - other
319 } else {
320 other - self
321 }
322 }
323
324 pub fn to_u128_floor(self) -> Option<u128> {
325 let truncated = self.repr >> FRACTIONAL_BITS;
326 if truncated.bits() <= 128 {
327 Some(truncated.as_u128())
328 } else {
329 None
330 }
331 }
332
333 pub fn to_u128_ceil(self) -> Option<u128> {
334 let truncated = self.repr >> FRACTIONAL_BITS;
335 if truncated.bits() <= 128 {
336 if self.fractional_part().is_zero() {
337 Some(truncated.as_u128())
338 } else {
339 truncated.as_u128().checked_add(1)
340 }
341 } else {
342 None
343 }
344 }
345
346 #[allow(
347 clippy::cast_precision_loss,
348 clippy::cast_possible_truncation,
349 clippy::cast_possible_wrap,
350 reason = "Lossiness is acceptable for this function"
351 )]
352 pub fn to_f64_lossy(self) -> f64 {
353 let frac = self.repr.low_u128() as f64 / f64_pow2(FRACTIONAL_BITS as i32);
354 let low = (self.repr >> FRACTIONAL_BITS).low_u128() as f64;
355 let high = (self.repr >> (FRACTIONAL_BITS * 2)).low_u128() as f64 * f64_pow2(128);
356
357 high + low + frac
358 }
359
360 pub fn to_fixed(&self, precision: usize) -> String {
361 let precision = precision.min(FRACTIONAL_DECIMAL_DIGITS);
362 let (fractional_part, overflow) = self.fractional_part_to_dec_string(precision, false);
363 let fractional_part_trimmed = fractional_part.trim_end_matches('0');
364 let repr = if overflow {
365 self.repr.saturating_add(Self::ONE.repr)
366 } else {
367 self.repr
368 };
369 if fractional_part_trimmed.is_empty() {
370 format!("{}", repr >> FRACTIONAL_BITS)
371 } else {
372 format!("{}.{fractional_part_trimmed}", repr >> FRACTIONAL_BITS)
373 }
374 }
375
376 fn fractional_part(&self) -> U512 {
377 U512([self.repr.0[0], self.repr.0[1], 0, 0, 0, 0, 0, 0])
378 }
379
380 pub fn fractional_part_as_u128_dividend(&self) -> u128 {
381 u128::from(self.repr.0[0]) | (u128::from(self.repr.0[1]) << 64)
382 }
383
384 fn epsilon_round(repr: U512) -> U512 {
385 (repr + (Self::REPR_EPSILON >> 1)) & !(Self::REPR_EPSILON - 1)
386 }
387
388 fn fractional_part_to_dec_string(&self, precision: usize, round_up: bool) -> (String, bool) {
389 let mut s = Vec::with_capacity(precision);
390 let mut f = self.fractional_part();
391 let mut overflow = false;
392
393 if round_up {
394 let plus_two = f.saturating_add(2.into());
395 overflow = plus_two.0[2] != 0;
396 f = U512([plus_two.0[0], plus_two.0[1], 0, 0, 0, 0, 0, 0]);
397 }
398
399 for _ in 0..precision {
400 if f.is_zero() {
401 break;
402 }
403
404 f *= 10;
405
406 let digit = (f / Self::ONE.repr).low_u64();
407 #[allow(clippy::cast_possible_truncation)]
408 s.push(digit as u8 + b'0');
409
410 f %= Self::ONE.repr;
411 }
412
413 if !round_up && !f.is_zero() && (U512::MAX - 2 >= self.repr) {
414 return self.fractional_part_to_dec_string(precision, true);
415 }
416
417 (unsafe { String::from_utf8_unchecked(s) }, overflow)
419 }
420}
421
422#[inline]
423#[allow(clippy::cast_sign_loss)]
424const fn f64_pow2(exponent: i32) -> f64 {
426 debug_assert!(
427 !(exponent < -1022 || exponent > 1023),
428 "Exponent out of range for f64"
429 );
430 f64::from_bits(((1023 + exponent) as u64) << 52)
431}
432
433pub mod error {
434 use thiserror::Error;
435
436 #[derive(Debug, Error)]
437 #[error("Failed to parse decimal")]
438 pub struct DecimalParseError;
439}
440
441impl FromStr for Decimal {
442 type Err = error::DecimalParseError;
443
444 fn from_str(s: &str) -> Result<Self, Self::Err> {
445 let (whole, frac) = match s.split_once('.') {
446 Some((whole, frac)) if !frac.contains('.') => (whole, Some(frac)),
447 Some(_) => return Err(error::DecimalParseError),
448 None => (s, None),
449 };
450
451 let canonical_whole = whole == "0"
452 || whole
453 .as_bytes()
454 .split_first()
455 .is_some_and(|(&first, rest)| {
456 first.is_ascii_digit() && first != b'0' && rest.iter().all(u8::is_ascii_digit)
457 });
458 if !canonical_whole {
459 return Err(error::DecimalParseError);
460 }
461
462 if let Some(frac) = frac {
463 if frac.is_empty()
464 || frac.len() > FRACTIONAL_DECIMAL_DIGITS
465 || !frac.as_bytes().iter().all(u8::is_ascii_digit)
466 {
467 return Err(error::DecimalParseError);
468 }
469 }
470
471 let whole = U512::from_dec_str(whole).map_err(|_| error::DecimalParseError)?;
472 if whole > MAX_WHOLE {
473 return Err(error::DecimalParseError);
474 }
475 let whole = whole << FRACTIONAL_BITS;
476
477 let Some(frac) = frac else {
478 return Ok(Self { repr: whole });
479 };
480
481 let mut fractional = U512::zero();
482 let mut divisor = 10u128;
483 for digit in frac.bytes().map(|digit| digit - b'0') {
484 if digit != 0 {
485 fractional += (U512::from(digit) << (FRACTIONAL_BITS * 2)) / divisor;
486 }
487 if let Some(next_divisor) = divisor.checked_mul(10) {
488 divisor = next_divisor;
489 }
490 }
491 let fractional = Decimal::epsilon_round(fractional >> FRACTIONAL_BITS);
492 let (repr, overflowed) = whole.overflowing_add(fractional);
493 if overflowed {
494 return Ok(Self::MAX);
495 }
496
497 Ok(Self { repr })
498 }
499}
500
501impl Display for Decimal {
502 fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
503 write!(f, "{}", self.to_f64_lossy())
504 }
505}
506
507impl Debug for Decimal {
508 fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
509 write!(f, "{}", self.to_fixed(FRACTIONAL_DECIMAL_DIGITS))
510 }
511}
512
513macro_rules! impl_self {
514 ($s:ty,$t:ty) => {
515 impl Add<$t> for $s {
516 type Output = Decimal;
517
518 fn add(self, rhs: $t) -> Decimal {
519 Decimal {
520 repr: self.repr.add(rhs.repr),
521 }
522 }
523 }
524
525 impl Sub<$t> for $s {
526 type Output = Decimal;
527
528 fn sub(self, rhs: $t) -> Decimal {
529 Decimal {
530 repr: self.repr.sub(rhs.repr),
531 }
532 }
533 }
534
535 impl Mul<$t> for $s {
536 type Output = Decimal;
537
538 fn mul(self, rhs: $t) -> Decimal {
539 #[allow(clippy::cast_possible_truncation)]
540 let mut shr = FRACTIONAL_BITS as u32;
541 let shr_self = self.repr.trailing_zeros().min(shr);
542 let self_repr = self.repr >> shr_self;
543 shr -= shr_self;
544 let shr_rhs = rhs.repr.trailing_zeros().min(shr);
545 let rhs_repr = rhs.repr >> shr_rhs;
546 shr -= shr_rhs;
547 Decimal {
548 repr: (self_repr * rhs_repr) >> shr,
549 }
550 }
551 }
552
553 impl Div<$t> for $s {
554 type Output = Decimal;
555
556 fn div(self, rhs: $t) -> Decimal {
557 #[allow(clippy::cast_possible_truncation)]
558 let mut sh = FRACTIONAL_BITS as u32;
559 let sh_self = self.repr.leading_zeros().min(sh);
560 let self_repr = self.repr << sh_self;
561 sh -= sh_self;
562 let sh_rhs = rhs.repr.trailing_zeros().min(sh);
563 let rhs_repr = rhs.repr >> sh_rhs;
564 sh -= sh_rhs;
565 Decimal {
566 repr: (self_repr / rhs_repr) << sh,
567 }
568 }
569 }
570 };
571}
572
573impl_self!(Decimal, Decimal);
574impl_self!(&Decimal, Decimal);
575impl_self!(Decimal, &Decimal);
576impl_self!(&Decimal, &Decimal);
577
578macro_rules! impl_self_assign {
579 ($s:ty,$t:ty) => {
580 impl AddAssign<$t> for $s {
581 fn add_assign(&mut self, rhs: $t) {
582 self.repr += rhs.repr;
583 }
584 }
585
586 impl SubAssign<$t> for $s {
587 fn sub_assign(&mut self, rhs: $t) {
588 self.repr -= rhs.repr;
589 }
590 }
591
592 impl DivAssign<$t> for $s {
593 fn div_assign(&mut self, rhs: $t) {
594 self.repr = (*self / rhs).repr;
595 }
596 }
597
598 impl MulAssign<$t> for $s {
599 fn mul_assign(&mut self, rhs: $t) {
600 self.repr = (*self * rhs).repr;
601 }
602 }
603 };
604}
605
606impl_self_assign!(Decimal, Decimal);
607impl_self_assign!(Decimal, &Decimal);
608
609macro_rules! impl_int {
610 ($t:ty) => {
611 impl_int!(@from $t);
612 impl_int!(@ops $t, Decimal);
613 impl_int!(@ops $t, &Decimal);
614 };
615
616 (@from $t:ty) => {
617 impl From<$t> for Decimal {
618 fn from(value: $t) -> Self {
619 Self {
620 repr: U512::from(value) << FRACTIONAL_BITS,
621 }
622 }
623 }
624 };
625
626 (@ops $t:ty,$s:ty) => {
627 impl Mul<$t> for $s {
628 type Output = Decimal;
629
630 fn mul(self, rhs: $t) -> Decimal {
631 Decimal { repr: self.repr * U512::from(rhs) }
632 }
633 }
634
635 impl Mul<$s> for $t {
636 type Output = Decimal;
637
638 fn mul(self, rhs: $s) -> Decimal {
639 Decimal { repr: U512::from(self) * rhs.repr }
640 }
641 }
642
643 impl Div<$t> for $s {
644 type Output = Decimal;
645
646 fn div(self, rhs: $t) -> Decimal {
647 Decimal { repr: self.repr / U512::from(rhs) }
648 }
649 }
650
651 impl Div<$s> for $t {
652 type Output = Decimal;
653
654 fn div(self, rhs: $s) -> Decimal {
655 Decimal::from(self) / rhs
656 }
657 }
658
659 impl Add<$t> for $s {
660 type Output = Decimal;
661
662 fn add(self, rhs: $t) -> Decimal {
663 self + Decimal::from(rhs)
664 }
665 }
666
667 impl Add<$s> for $t {
668 type Output = Decimal;
669
670 fn add(self, rhs: $s) -> Decimal {
671 Decimal::from(self) + rhs
672 }
673 }
674
675 impl Sub<$t> for $s {
676 type Output = Decimal;
677
678 fn sub(self, rhs: $t) -> Decimal {
679 self - Decimal::from(rhs)
680 }
681 }
682
683 impl Sub<$s> for $t {
684 type Output = Decimal;
685
686 fn sub(self, rhs: $s) -> Decimal {
687 Decimal::from(self) - rhs
688 }
689 }
690
691 impl PartialEq<$t> for $s {
692 fn eq(&self, other: &$t) -> bool {
693 self.repr == Decimal::from(*other).repr
694 }
695 }
696
697 impl PartialOrd<$t> for $s {
698 fn partial_cmp(&self, other: &$t) -> Option<core::cmp::Ordering> {
699 self.repr.partial_cmp(&Decimal::from(*other).repr)
700 }
701 }
702 };
703}
704
705impl_int!(u8);
706impl_int!(u16);
707impl_int!(u32);
708impl_int!(u64);
709impl_int!(u128);
710impl_int!(::primitive_types::U256);
711
712macro_rules! impl_from_const {
713 ($t:ty,$name:ident) => {
714 impl Decimal {
715 pub const fn $name(value: $t) -> Self {
716 Self {
717 repr: U512([0, 0, value as u64, 0, 0, 0, 0, 0]),
718 }
719 }
720 }
721 };
722}
723
724impl_from_const!(u8, from_u8);
725impl_from_const!(u16, from_u16);
726impl_from_const!(u32, from_u32);
727impl_from_const!(u64, from_u64);
728
729#[cfg(test)]
730mod tests {
731 use alloc::string::ToString;
732 use primitive_types::U256;
733 use rand::Rng;
734 use rstest::rstest;
735
736 use super::*;
737
738 macro_rules! println {
739 ($($arg:tt)*) => {
740 #[cfg(feature = "std")]
741 std::println!($($arg)*);
742 #[cfg(not(feature = "std"))]
743 let _ = format_args!($($arg)*);
744 };
745 }
746
747 fn with_upper_u128(n: u128) -> Decimal {
752 let mut d = Decimal::from(n);
753 d *= Decimal::from(u128::pow(2, 64));
754 d *= Decimal::from(u128::pow(2, 64));
755 d
756 }
757
758 fn get_upper_u128(mut d: Decimal) -> u128 {
759 d /= Decimal::from(u128::pow(2, 64));
760 d /= Decimal::from(u128::pow(2, 64));
761 d.to_u128_floor().unwrap()
762 }
763
764 #[rstest]
765 #[test]
766 fn const_constructors_are_correct() {
767 assert_eq!(Decimal::from_u8(0).to_u128_ceil().unwrap(), 0);
768 assert_eq!(Decimal::from_u8(u8::MAX).to_u128_ceil().unwrap(), 0xff);
769 assert_eq!(Decimal::from_u16(0).to_u128_ceil().unwrap(), 0);
770 assert_eq!(Decimal::from_u16(u16::MAX).to_u128_ceil().unwrap(), 0xffff);
771 assert_eq!(Decimal::from_u32(0).to_u128_ceil().unwrap(), 0);
772 assert_eq!(
773 Decimal::from_u32(u32::MAX).to_u128_ceil().unwrap(),
774 0xffff_ffff,
775 );
776 assert_eq!(Decimal::from_u64(0).to_u128_ceil().unwrap(), 0);
777 assert_eq!(
778 Decimal::from_u64(u64::MAX).to_u128_ceil().unwrap(),
779 0xffff_ffff_ffff_ffff,
780 );
781 }
782
783 #[rstest]
784 #[case(0, 0)]
785 #[case(0, 1)]
786 #[case(1, 0)]
787 #[case(1, 1)]
788 #[case(2_934_570_000_008_u128, 9_595_959_283_u128)]
789 #[case(u128::MAX, 0)]
790 #[case(0, u128::MAX)]
791 #[test]
792 fn addition(#[case] a: u128, #[case] b: u128) {
793 assert_eq!(Decimal::from(a) + Decimal::from(b), a + b);
794 assert_eq!(
795 get_upper_u128(with_upper_u128(a) + with_upper_u128(b)),
796 a + b,
797 );
798 }
799
800 #[rstest]
801 #[case(0, 0)]
802 #[case(1, 0)]
803 #[case(1, 1)]
804 #[case(2_934_570_000_008_u128, 9_595_959_283_u128)]
805 #[case(u128::MAX, 0)]
806 #[case(u128::MAX, 1)]
807 #[case(u128::MAX, u128::MAX / 2)]
808 #[case(u128::MAX, u128::MAX)]
809 #[test]
810 fn subtraction(#[case] a: u128, #[case] b: u128) {
811 assert_eq!(Decimal::from(a) - Decimal::from(b), a - b);
812 assert_eq!(
813 get_upper_u128(with_upper_u128(a) - with_upper_u128(b)),
814 a - b,
815 );
816 }
817
818 #[rstest]
819 #[case(0, 0)]
820 #[case(0, 1)]
821 #[case(1, 0)]
822 #[case(1, 1)]
823 #[case(2, 2)]
824 #[case(u128::MAX, 0)]
825 #[case(u128::MAX, 1)]
826 #[case(0, u128::MAX)]
827 #[case(1, u128::MAX)]
828 #[test]
829 fn multiplication(#[case] a: u128, #[case] b: u128) {
830 assert_eq!(Decimal::from(a) * Decimal::from(b), a * b);
831 assert_eq!(get_upper_u128(with_upper_u128(a) * b), a * b);
832 assert_eq!(get_upper_u128(a * with_upper_u128(b)), a * b);
833 }
834
835 #[rstest]
836 #[case(0, 1)]
837 #[case(1, 1)]
838 #[case(1, 2)]
839 #[case(u128::MAX, u128::MAX)]
840 #[case(u128::MAX, 1)]
841 #[case(0, u128::MAX)]
842 #[case(1, u128::MAX)]
843 #[case(1, 10)]
844 #[case(3, 10_000)]
845 #[test]
846 fn division(#[case] a: u128, #[case] b: u128) {
847 #[allow(clippy::cast_precision_loss)]
848 let quotient = a as f64 / b as f64;
849 let abs_difference_lte = |d: Decimal, f: f64| (d.to_f64_lossy() - f).abs() <= 1e-200;
850 assert!(abs_difference_lte(
851 Decimal::from(a) / Decimal::from(b),
852 quotient,
853 ));
854 assert!(abs_difference_lte(
855 with_upper_u128(a) / with_upper_u128(b),
856 quotient,
857 ));
858 }
859
860 #[rstest]
861 #[case(12, 2)]
862 #[case(2, 32)]
863 #[case(1, 0)]
864 #[case(0, 0)]
865 #[case(0, 1)]
866 #[case(1, 1)]
867 #[test]
868 fn power(#[case] x: u128, #[case] n: u32) {
869 #[allow(clippy::cast_possible_wrap)]
870 let n_i32 = n as i32;
871 assert_eq!(Decimal::from(x).pow(n_i32), Decimal::from(x.pow(n)));
872 }
873
874 #[test]
875 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
876 fn pow10_valid_range() {
877 assert_eq!(
878 Decimal::ONE.mul_pow10(-(FRACTIONAL_DECIMAL_DIGITS as i32) - 1),
879 None,
880 );
881 for i in -(FRACTIONAL_DECIMAL_DIGITS as i32)..=(WHOLE_DECIMAL_DIGITS as i32) {
882 println!("10^{i} = {:?}", Decimal::ONE.mul_pow10(i).unwrap());
883 }
884 assert_eq!(
885 Decimal::ONE.mul_pow10((WHOLE_DECIMAL_DIGITS as i32) + 1),
886 None,
887 );
888 }
889
890 #[rstest]
891 #[case(0, 0)]
892 #[case(0, 1)]
893 #[case(0, -1)]
894 #[case(1, 0)]
895 #[case(1, 1)]
896 #[case(1, -1)]
897 #[case(1, i32::try_from(WHOLE_DECIMAL_DIGITS).unwrap())]
898 #[case(1, i32::try_from(FRACTIONAL_DECIMAL_DIGITS).unwrap())]
899 #[case(1, -i32::try_from(FRACTIONAL_DECIMAL_DIGITS).unwrap())]
900 #[case(12, 20)]
901 #[case(12, 0)]
902 #[case(12, -20)]
903 #[case(u128::MAX, 0)]
904 #[case(u128::MAX, -20)]
905 #[test]
906 fn mul_pow10(#[case] x: u128, #[case] n: i32) {
907 #[allow(clippy::cast_sign_loss)]
908 if n >= 0 {
909 assert_eq!(
910 Decimal::from(x).mul_pow10(n).unwrap(),
911 Decimal::from(x) * Decimal::from(10u32).pow(n),
912 );
913 } else {
914 assert!(Decimal::from(x)
915 .mul_pow10(n)
916 .unwrap()
917 .near_equal(Decimal::from(x) / U256::exp10(-n as usize)));
918 }
919 }
920
921 #[test]
922 fn constants_are_accurate() {
923 assert_eq!(Decimal::ZERO.to_u128_floor().unwrap(), 0);
924 assert!((Decimal::ONE_HALF.to_f64_lossy() - 0.5_f64).abs() < 1e-200);
925 assert_eq!(Decimal::ONE.to_u128_floor().unwrap(), 1);
926 assert_eq!(Decimal::TWO.to_u128_floor().unwrap(), 2);
927 }
928
929 #[rstest]
930 #[case(Decimal::ONE, 0)]
931 #[case(Decimal::ONE_HALF, 1u128 << 127)]
932 #[test]
933 fn get_fractional_dividend(#[case] value: Decimal, #[case] expected: u128) {
934 assert_eq!(value.fractional_part_as_u128_dividend(), expected);
935 }
936
937 #[cfg(feature = "serde")]
938 #[rstest]
939 #[case(Decimal::ONE)]
940 #[case(Decimal::TWO)]
941 #[case(Decimal::ZERO)]
942 #[case(Decimal::ONE_HALF)]
943 #[case(Decimal::from(u128::MAX))]
944 #[case(Decimal::from(u64::MAX) / Decimal::from(u128::MAX))]
945 #[case(Decimal::MAX)]
946 #[test]
947 fn serialization(#[case] value: Decimal) {
948 let serialized = serde_json::to_string(&value).unwrap();
949 let deserialized: Decimal = serde_json::from_str(&serialized).unwrap();
950
951 assert!(value.near_equal(deserialized));
952 }
953
954 #[test]
955 fn from_self_string_serialization_precision() {
956 const ITERATIONS: usize = 1_024;
957 const TRANSFORMATIONS: usize = 16;
958
959 let mut rng = rand::thread_rng();
960
961 let mut max_error = U512::zero();
962 let mut error_distribution = [0u32; 16];
963 let mut value_with_max_error = Decimal::ZERO;
964
965 #[allow(clippy::cast_possible_truncation)]
966 for _ in 0..ITERATIONS {
967 let actual = Decimal {
968 repr: U512(rng.gen()),
969 };
970
971 let mut s = actual.to_fixed(FRACTIONAL_DECIMAL_DIGITS);
972 for _ in 0..(TRANSFORMATIONS - 1) {
973 s = Decimal::from_str(&s)
974 .unwrap()
975 .to_fixed(FRACTIONAL_DECIMAL_DIGITS);
976 }
977 let parsed = Decimal::from_str(&s).unwrap();
978
979 let e = actual.abs_diff(parsed).repr;
980
981 if e > max_error {
982 max_error = e;
983 value_with_max_error = actual;
984 }
985
986 error_distribution[e.0[0] as usize] += 1;
987 }
988
989 println!("Error distribution:");
990 for (i, x) in error_distribution.iter().enumerate() {
991 println!("\t{i}: {x:b}");
992 }
993 println!("Max error: {:?}", max_error.0);
994
995 assert!(
996 max_error <= Decimal::REPR_EPSILON,
997 "Stringification error of repr {:?} is repr {:?}",
998 value_with_max_error.repr.0,
999 max_error.0,
1000 );
1001 }
1002
1003 #[test]
1004 #[allow(clippy::cast_precision_loss)]
1005 fn from_f64_string_serialization_precision() {
1006 const ITERATIONS: usize = 10_000;
1007 let mut rng = rand::thread_rng();
1008 let epsilon = Decimal {
1009 repr: Decimal::REPR_EPSILON,
1010 }
1011 .to_f64_lossy();
1012
1013 let t = |f: f64| {
1014 let actual = f.abs();
1015 let string = actual.to_string();
1016 let parsed = Decimal::from_str(&string).unwrap();
1017
1018 let e = (parsed.to_f64_lossy() - actual).abs();
1019
1020 assert!(e <= epsilon, "Stringification error of f64 {actual} is {e}");
1021 };
1022
1023 for _ in 0..ITERATIONS {
1024 t(rng.gen::<f64>() * rng.gen::<u128>() as f64);
1025 }
1026 }
1027
1028 #[test]
1029 fn hal_29_rejects_noncanonical_or_partial_decimal_text() {
1030 let malformed = [
1031 "",
1032 ".1",
1033 "1.",
1034 "01",
1035 "+1",
1036 "-1",
1037 " 1",
1038 "1 ",
1039 "1e1",
1040 "1_0",
1041 "1.0.0",
1042 "1.000000000000000000000000000000000000000",
1043 "١",
1044 ];
1045
1046 for value in malformed {
1047 assert!(Decimal::from_str(value).is_err(), "{value:?} was accepted");
1048 }
1049 }
1050
1051 #[test]
1052 fn decimal_max_fixed_text_round_trips() {
1053 assert_eq!(
1054 Decimal::MAX
1055 .to_fixed(FRACTIONAL_DECIMAL_DIGITS)
1056 .parse::<Decimal>()
1057 .unwrap(),
1058 Decimal::MAX
1059 );
1060 }
1061
1062 #[test]
1063 fn hal_34_enforces_the_exact_whole_range() {
1064 let maximum = MAX_WHOLE.to_string();
1065 assert!(Decimal::from_str(&maximum).is_ok());
1066 assert!(Decimal::from_str(&format!("{maximum}.1")).is_ok());
1067
1068 let first_out_of_range = (MAX_WHOLE + U512::one()).to_string();
1069 assert!(Decimal::from_str(&first_out_of_range).is_err());
1070 assert!(Decimal::from_str(&U512::MAX.to_string()).is_err());
1071
1072 let carrying_fraction = format!("{maximum}.99999999999999999999999999999999999999");
1073 assert_eq!(Decimal::from_str(&carrying_fraction).unwrap(), Decimal::MAX);
1074 }
1075
1076 #[cfg(feature = "serde")]
1077 #[test]
1078 fn hal_34_serde_uses_decimal_range_validation() {
1079 let first_out_of_range = (MAX_WHOLE + U512::one()).to_string();
1080 let encoded = serde_json::to_string(&first_out_of_range).unwrap();
1081 assert!(serde_json::from_str::<Decimal>(&encoded).is_err());
1082
1083 let carrying_fraction = format!("{MAX_WHOLE}.99999999999999999999999999999999999999");
1084 let encoded = serde_json::to_string(&carrying_fraction).unwrap();
1085 assert_eq!(
1086 serde_json::from_str::<Decimal>(&encoded).unwrap(),
1087 Decimal::MAX
1088 );
1089 }
1090
1091 #[cfg(feature = "schemars")]
1092 #[test]
1093 fn hal_34_schema_matches_decimal_parser_boundaries() {
1094 let schema = schemars::schema_for!(Decimal);
1095 let pattern = schema
1096 .schema
1097 .string
1098 .as_ref()
1099 .unwrap()
1100 .pattern
1101 .as_ref()
1102 .unwrap();
1103 let regex = regex::Regex::new(pattern).unwrap();
1104 let maximum = MAX_WHOLE.to_string();
1105 let cases = [
1106 ("0".to_string(), true),
1107 ("1.0".to_string(), true),
1108 (maximum.clone(), true),
1109 (
1110 format!("{maximum}.99999999999999999999999999999999999999"),
1111 true,
1112 ),
1113 ((MAX_WHOLE + U512::one()).to_string(), false),
1114 (U512::MAX.to_string(), false),
1115 ("01".to_string(), false),
1116 ("1.".to_string(), false),
1117 ("1e1".to_string(), false),
1118 ];
1119
1120 for (value, expected) in cases {
1121 assert_eq!(Decimal::from_str(&value).is_ok(), expected, "{value}");
1122 assert_eq!(regex.is_match(&value), expected, "{value}");
1123 }
1124 }
1125
1126 #[test]
1127 fn round_up_repr() {
1128 let cases = [
1129 Decimal {
1130 #[rustfmt::skip]
1131 repr: U512([ 0x0966_4E4C_9169_501F, 0xB226_2812_5CF2_3CD0, 1, 0, 0, 0, 0, 0 ]),
1132 },
1133 Decimal {
1134 repr: U512([u64::MAX, u64::MAX, 1, 0, 0, 0, 0, 0]),
1135 },
1137 Decimal {
1138 repr: U512([u64::MAX - 1, u64::MAX, 1, 0, 0, 0, 0, 0]),
1139 },
1140 Decimal { repr: U512::zero() },
1141 Decimal::MAX,
1142 ];
1143
1144 for case in cases {
1145 let p: Decimal = case.to_fixed(FRACTIONAL_DECIMAL_DIGITS).parse().unwrap();
1146
1147 println!("{:x?}", case.repr.0);
1148 println!("{:x?}", p.repr.0);
1149 println!("|{p:?} - {case:?}| = {:?}", p.abs_diff(case).as_repr());
1150
1151 assert!(p.near_equal(case));
1152 }
1153 }
1154
1155 #[test]
1156 fn round_up_str() {
1157 let cases = [
1159 "1",
1160 "0",
1161 "1.6958947224456518",
1162 "2.79",
1163 "0.6",
1164 "10.6",
1165 "0.01",
1166 "0.599999999999999999999999999999999999",
1167 ];
1168 for case in cases {
1169 println!("Testing {case}...");
1170 let n = Decimal::from_str(case).unwrap();
1171 let s = n.to_fixed(FRACTIONAL_DECIMAL_DIGITS);
1172 let parsed = Decimal::from_str(&s).unwrap();
1173 assert_eq!(n, parsed);
1174 println!("{n:?}");
1175 println!("");
1176 }
1177 }
1178
1179 #[test]
1180 #[allow(clippy::float_cmp)]
1181 fn const_pow2() {
1182 for i in -1022..=1023 {
1183 let f = f64_pow2(i);
1184 let e = 2f64.powi(i);
1185 assert_eq!(f, e);
1186 }
1187 }
1188}