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