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