1use std::cmp::{max, Ordering};
2
3use bigdecimal::{BigDecimal, Signed};
4use num_bigint::{BigInt, BigUint, ToBigInt, ToBigUint};
5
6use crate::ion_data::{IonEq, IonOrd};
7use crate::result::{illegal_operation, illegal_operation_raw, IonError};
8use crate::types::{Coefficient, Sign, UInt};
9use num_integer::Integer;
10use num_traits::{ToPrimitive, Zero};
11use std::convert::{TryFrom, TryInto};
12use std::fmt::{Display, Formatter};
13use std::ops::Neg;
14
15#[derive(Clone, Debug)]
17pub struct Decimal {
18 pub(crate) coefficient: Coefficient,
20 pub(crate) exponent: i64,
21}
22
23impl Decimal {
24 pub fn new<I: Into<Coefficient>>(coefficient: I, exponent: i64) -> Decimal {
27 let coefficient = coefficient.into();
28 Decimal {
29 coefficient,
30 exponent,
31 }
32 }
33
34 pub fn scale(&self) -> i64 {
39 self.exponent.neg()
40 }
41
42 pub fn precision(&self) -> u64 {
44 if self.exponent > 0 {
45 return self.coefficient.number_of_decimal_digits() + self.exponent as u64;
46 }
47 self.coefficient.number_of_decimal_digits()
48 }
49
50 pub fn negative_zero() -> Decimal {
53 Decimal::negative_zero_with_exponent(0)
54 }
55
56 pub fn negative_zero_with_exponent(exponent: i64) -> Decimal {
60 let coefficient = Coefficient::negative_zero();
61 Decimal {
62 coefficient,
63 exponent,
64 }
65 }
66
67 pub fn is_zero(&self) -> bool {
69 match self.coefficient.magnitude() {
70 UInt::U64(0) => true,
71 UInt::BigUInt(m) => m.is_zero(),
72 _ => false,
73 }
74 }
75
76 pub fn is_less_than_zero(&self) -> bool {
79 match (self.coefficient.sign(), self.coefficient.magnitude()) {
80 (Sign::Negative, UInt::U64(m)) if *m > 0 => true,
81 (Sign::Negative, UInt::BigUInt(m)) if m > &BigUint::zero() => true,
82 _ => false,
83 }
84 }
85
86 pub(crate) fn is_greater_than_or_equal_to_one(&self) -> bool {
88 match &self.coefficient.magnitude {
91 UInt::U64(magnitude) if magnitude.is_zero() => return false,
92 UInt::BigUInt(magnitude) if magnitude.is_zero() => return false,
93 _ => {}
94 }
95
96 if self.exponent >= 0 {
99 return true;
100 }
101
102 let num_coefficient_decimal_digits = self.coefficient.number_of_decimal_digits();
105 num_coefficient_decimal_digits > self.exponent.unsigned_abs()
106 }
107
108 fn compare(d1: &Decimal, d2: &Decimal) -> Ordering {
111 if d1.is_zero() && d2.is_zero() {
112 return Ordering::Equal;
114 }
115 let sign_cmp = d1.coefficient.sign().cmp(&d2.coefficient.sign());
119 if sign_cmp != Ordering::Equal {
120 return sign_cmp;
121 }
122
123 let ordering = Decimal::compare_magnitudes(d1, d2);
125
126 if d1.coefficient.sign() == Sign::Positive {
127 ordering
129 } else {
130 ordering.reverse()
134 }
135 }
136
137 fn compare_magnitudes(d1: &Decimal, d2: &Decimal) -> Ordering {
139 if d1.exponent == d2.exponent {
141 return d1.coefficient.magnitude().cmp(d2.coefficient.magnitude());
142 }
143
144 if d1.exponent > d2.exponent {
150 Self::compare_scaled_coefficients(d1, d2)
151 } else {
152 Self::compare_scaled_coefficients(d2, d1).reverse()
153 }
154 }
155
156 fn compare_scaled_coefficients(d1: &Decimal, d2: &Decimal) -> Ordering {
159 let exponent_delta = d1.exponent - d2.exponent;
160 let mut scaled_coefficient: BigUint = d1.coefficient.magnitude().to_biguint().unwrap();
168 scaled_coefficient *= BigUint::from(10u64).pow(exponent_delta as u32);
169 UInt::BigUInt(scaled_coefficient).cmp(d2.coefficient.magnitude())
170 }
171
172 pub(crate) fn into_parts(self) -> (BigInt, (BigInt, i64)) {
184 let magnitude: BigInt = self.coefficient.try_into().unwrap();
185 if self.exponent.is_zero() {
186 (magnitude, (BigInt::zero(), 0))
187 } else if self.exponent.is_negative() {
188 let divisor = BigInt::from(10u64).pow((-self.exponent) as u32);
189 let (i, f) = magnitude.div_rem(&divisor);
190 (i, (f, self.exponent))
191 } else {
192 let multiplicand = BigInt::from(10u64).pow(self.exponent as u32);
193 (magnitude * multiplicand, (BigInt::zero(), 0))
194 }
195 }
196
197 pub(crate) fn into_parts_lossy(self) -> (BigInt, f64) {
210 fn to_fract(frac: BigInt, exponent: i64) -> f64 {
212 if frac.is_zero() {
213 0.0
214 } else {
215 frac.to_f64().unwrap() / 10f64.powi(max(0, -exponent) as i32)
216 }
217 }
218 let (i, (f, e)) = self.into_parts();
219 (i, to_fract(f, e))
220 }
221
222 pub(crate) fn difference_by_parts_lossy(d1: &Decimal, d2: &Decimal) -> (BigInt, f64) {
240 let (d1_int, d1_frac) = d1.clone().into_parts_lossy();
241 let (d2_int, d2_frac) = d2.clone().into_parts_lossy();
242
243 ((d1_int - d2_int), (d1_frac - d2_frac))
244 }
245}
246
247impl PartialEq for Decimal {
248 fn eq(&self, other: &Self) -> bool {
249 self.cmp(other) == Ordering::Equal
250 }
251}
252
253impl Eq for Decimal {}
254
255impl IonEq for Decimal {
256 fn ion_eq(&self, other: &Self) -> bool {
257 self.exponent == other.exponent && self.coefficient == other.coefficient
258 }
259}
260
261impl IonOrd for Decimal {
262 fn ion_cmp(&self, other: &Self) -> Ordering {
264 let sign_cmp = self.coefficient.sign().cmp(&other.coefficient.sign());
265 if sign_cmp != Ordering::Equal {
266 return sign_cmp;
267 }
268
269 let ordering = Decimal::compare_magnitudes(self, other);
271 if ordering != Ordering::Equal {
272 return match self.coefficient.sign {
273 Sign::Negative => ordering.reverse(),
274 Sign::Positive => ordering,
275 };
276 };
277 self.exponent.cmp(&other.exponent).reverse()
280 }
281}
282
283impl PartialOrd for Decimal {
284 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
285 Some(self.cmp(other))
286 }
287}
288
289impl Ord for Decimal {
290 fn cmp(&self, other: &Self) -> Ordering {
291 Decimal::compare(self, other)
292 }
293}
294
295macro_rules! impl_decimal_from_unsigned_primitive_integer {
296 ($($t:ty),*) => ($(
297 impl From<$t> for Decimal {
298 fn from(value: $t) -> Self {
299 Decimal::new(value as u64, 0)
300 }
301 }
302 )*)
303}
304impl_decimal_from_unsigned_primitive_integer!(u8, u16, u32, u64, usize);
305
306macro_rules! impl_decimal_from_signed_primitive_integer {
307 ($($t:ty),*) => ($(
308 impl From<$t> for Decimal {
309 fn from(value: $t) -> Self {
310 let sign = if value < 0 {Sign::Negative} else {Sign::Positive};
311 let magnitude: u64 = value.checked_abs()
313 .and_then(|v| Some(v.abs() as u64))
314 .unwrap_or_else(|| (<$t>::MAX as u64) + 1);
317 let coefficient = Coefficient::new(sign, magnitude);
318 Decimal::new(coefficient, 0)
319 }
320 }
321 )*)
322}
323impl_decimal_from_signed_primitive_integer!(i8, i16, i32, i64, isize);
324
325impl TryFrom<f32> for Decimal {
326 type Error = IonError;
327
328 fn try_from(value: f32) -> Result<Self, Self::Error> {
329 (value as f64).try_into()
331 }
332}
333
334impl TryFrom<f64> for Decimal {
335 type Error = IonError;
336 fn try_from(value: f64) -> Result<Self, Self::Error> {
351 if value.is_infinite() {
352 if value.is_sign_negative() {
353 return illegal_operation("Cannot convert f64 negative infinity to Decimal.");
354 } else {
355 return illegal_operation("Cannot convert f64 infinity to Decimal.");
356 }
357 } else if value.is_nan() {
358 return illegal_operation("Cannot convert f64 NaN (not-a-number) to Decimal.");
359 }
360
361 if value == 0f64 {
363 if value.is_sign_negative() {
366 return Ok(Decimal::negative_zero());
367 }
368 return Ok(Decimal::new(0, 0));
369 }
370
371 fn try_convert(coefficient: f64, exponent: i64) -> Result<Decimal, IonError> {
372 let coefficient: Coefficient = if !coefficient.trunc().is_zero()
374 && coefficient.abs() <= i64::MAX as f64
375 {
376 (coefficient as i64).into()
377 } else {
378 coefficient
379 .to_bigint()
380 .ok_or_else(|| illegal_operation_raw("Cannot convert large f64 to Decimal."))?
381 .try_into()?
382 };
383
384 Ok(Decimal::new(coefficient, exponent))
385 }
386
387 if value.fract().is_zero() {
391 try_convert(value, 0)
392 } else {
393 const PRECISION: u32 = f64::DIGITS;
398
399 let leading_zeroes = (-1.0 - (value % 1.0).abs().log10().floor()).max(0.0) as u32;
402 let precision = (leading_zeroes + PRECISION).clamp(0, f64::MAX_10_EXP as u32);
403
404 let coefficient = value * 10f64.powi(precision as i32);
405 let exponent = -(precision as i64);
406 try_convert(coefficient, exponent)
407 }
408 }
409}
410
411impl Display for Decimal {
412 #[rustfmt::skip] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
414 const WIDE_NUMBER: usize = 6; let digits = &*self.coefficient.magnitude.to_string();
418 let len = digits.len();
419 let dot_index = len as i64 + self.exponent;
423
424 if self.coefficient.sign == Sign::Negative {
425 write!(f, "-").unwrap();
426 };
427
428 if self.exponent == 0 && len > WIDE_NUMBER { write!(f, "{}.{}d{}", &digits[0..1], &digits[1..len], (dot_index - 1))
430 } else if self.exponent == 0 { write!(f, "{}.", &digits)
432 } else if self.exponent >= 0 { write!(f, "{}d{}", &digits, self.exponent)
434 } else { if dot_index > 0 { let dot_index = dot_index as usize;
437 write!(f, "{}.{}", &digits[0..dot_index], &digits[dot_index..len])
438 } else if dot_index > -(WIDE_NUMBER as i64) { let width = dot_index.unsigned_abs() as usize + len;
440 write!(f, "0.{digits:0>width$}", width = width, digits = digits)
441 } else { write!(f, "{}.{}d{}", &digits[0..1], &digits[1..len], (dot_index - 1))
443 }
444 }
445 }
446}
447
448impl From<BigDecimal> for Decimal {
450 fn from(value: BigDecimal) -> Self {
451 let sign = if value.sign() == num_bigint::Sign::Minus {
452 Sign::Negative
453 } else {
454 Sign::Positive
455 };
456 let (big_int_coefficient, negative_exponent) = value.as_bigint_and_exponent();
457 let magnitude: BigUint = big_int_coefficient.abs().to_biguint().unwrap();
460 let exponent = -negative_exponent;
462
463 Decimal::new(Coefficient::new(sign, magnitude), exponent)
464 }
465}
466
467impl TryFrom<Decimal> for BigDecimal {
468 type Error = IonError;
469 fn try_from(value: Decimal) -> Result<Self, Self::Error> {
472 let coefficient_big_int: BigInt = value.coefficient.try_into()?;
474 Ok(BigDecimal::new(coefficient_big_int, -value.exponent))
475 }
476}
477
478#[cfg(test)]
479mod decimal_tests {
480 use crate::result::IonResult;
481 use crate::types::{Coefficient, Decimal, Sign, UInt};
482 use bigdecimal::BigDecimal;
483 use num_bigint::BigUint;
484
485 use num_traits::{Float, ToPrimitive};
486 use std::cmp::Ordering;
487 use std::convert::TryInto;
488 use std::fmt::Write;
489
490 use crate::ion_data::IonEq;
491
492 use rstest::*;
493
494 #[rstest]
495 #[case(Decimal::new(123, 1), "123d1")]
496 #[case(Decimal::new(123, 0), "123.")]
497 #[case(Decimal::new(-123, 0),"-123.")]
498 #[case(Decimal::new( 123, -1), "12.3")]
499 #[case(Decimal::new( 123, -3), "0.123")]
500 #[case(Decimal::new(-123, -5), "-0.00123")]
501 #[case(Decimal::new( 123, -5), "0.00123")]
502 #[case(Decimal::new( 123, -10), "1.23d-8")]
503 #[case(Decimal::new(-123, -10), "-1.23d-8")]
504 fn test_display(#[case] decimal: Decimal, #[case] expected: &str) {
505 let mut buffer = String::new();
506 write!(buffer, "{decimal}").unwrap();
507 assert_eq!(buffer.as_str(), expected);
508 }
509
510 #[test]
511 fn test_decimal_eq_negative_zeros() {
512 assert_eq!(Decimal::negative_zero(), Decimal::negative_zero());
514 assert_eq!(
515 Decimal::negative_zero_with_exponent(2),
516 Decimal::negative_zero_with_exponent(7)
517 );
518 assert_eq!(
519 Decimal::new(0, 0),
520 Decimal::new(Coefficient::new(Sign::Negative, 0), 0)
521 );
522 }
523
524 #[test]
525 fn test_decimal_ion_eq_negative_zeros() {
526 assert!(Decimal::negative_zero().ion_eq(&Decimal::negative_zero()));
528 assert!(!Decimal::negative_zero_with_exponent(2)
529 .ion_eq(&Decimal::negative_zero_with_exponent(7)));
530 assert!(!Decimal::new(0, 0).ion_eq(&Decimal::new(Coefficient::new(Sign::Negative, 0), 0)));
531 }
532
533 #[rstest]
534 #[case((80, 2), (80, 2), true)]
537 #[case((124, -2), (1240, -3), true)]
538 #[case((0, 0), (0, 0), true)]
539 #[case((0, -2), (0, 3), true)]
540 #[case((0, 2), (0, 5), true)]
541 fn test_decimal_eq<I: Into<Coefficient>>(
542 #[case] components1: (I, i64),
543 #[case] components2: (I, i64),
544 #[case] is_equal: bool,
545 ) {
546 let decimal1 = Decimal::new(components1.0.into(), components1.1);
547 let decimal2 = Decimal::new(components2.0.into(), components2.1);
548 assert_eq!(decimal1 == decimal2, is_equal);
549 }
550
551 #[rstest]
552 #[case((80, 2), (80, 2), true)]
555 #[case((124, -2), (124, -2), true)]
556 #[case((-124, -2), (-124, -2), true)]
557 #[case((124, -2), (1240, -3), false)]
558 #[case((0, 0), (0, 0), true)]
559 #[case((0, -2), (0, -3), false)]
560 #[case((0, -2), (0, 3), false)]
561 #[case((0, -2), (0, -2), true)]
562 #[case((0, 2), (0, 5), false)]
563 fn test_decimal_ion_eq<I: Into<Coefficient>>(
564 #[case] components1: (I, i64),
565 #[case] components2: (I, i64),
566 #[case] ion_eq_expected: bool,
567 ) {
568 let decimal1 = Decimal::new(components1.0.into(), components1.1);
569 let decimal2 = Decimal::new(components2.0.into(), components2.1);
570 assert_eq!(decimal1.ion_eq(&decimal2), ion_eq_expected);
571 }
572
573 #[rstest]
574 #[case((80, 3), Ordering::Equal, (80, 3))]
576 #[case((80, 3), Ordering::Greater, (-80, 3))]
577 #[case((80, 3), Ordering::Greater, (8, 3))]
578 #[case((80, 4), Ordering::Greater, (8, 3))]
579 #[case((-80, 4), Ordering::Equal, (-80, 4))]
580 #[case((-80, 4), Ordering::Less, (-8, 3))]
581 #[case((-80, 4), Ordering::Equal, (-8, 5))]
582 #[case((-1000, -1), Ordering::Less, (-99_999_999_999i64, -9))]
583 #[case((1000, -1), Ordering::Greater, (99_999_999_999i64, -9))]
584 fn test_decimal_ord<I: Into<Coefficient>>(
585 #[case] components1: (I, i64),
586 #[case] ordering: Ordering,
587 #[case] components2: (I, i64),
588 ) {
589 let decimal1 = Decimal::new(components1.0.into(), components1.1);
590 let decimal2 = Decimal::new(components2.0.into(), components2.1);
591 assert_eq!(decimal1.cmp(&decimal2), ordering);
592 assert_eq!(decimal2.cmp(&decimal1), ordering.reverse());
594 }
595
596 #[rstest]
597 #[case(0f64, Decimal::new(0, 0))]
598 #[case(f64::neg_zero(), Decimal::negative_zero())]
599 #[case(1f64, Decimal::new(1, 0))]
600 #[case(-1f64, Decimal::new(-1, 0))]
601 #[case(10f64, Decimal::new(1, 1))]
602 #[case(100f64, Decimal::new(1, 2))]
603 #[case(1.5f64, Decimal::new(15, -1))]
604 #[case(-1.5f64, Decimal::new(-15, -1))]
605 #[case(3.141592659f64, Decimal::new(3141592659i64, -9))]
606 #[case(-3.141592659f64, Decimal::new(-3141592659i64, -9))]
607 fn test_decimal_try_from_f64_ok(#[case] value: f64, #[case] expected: Decimal) {
608 let actual: Decimal = value.try_into().unwrap();
609 assert_eq!(actual, expected);
610 }
611
612 #[test]
613 fn test_difference_by_parts_lossy() {
614 let d1: Decimal = Decimal::new(123456789, -2);
615 let (int_part, frac_part) = d1.into_parts_lossy();
616 assert_eq!(int_part, num_bigint::BigInt::from(1234567u64));
617 assert_eq!(frac_part, 0.89);
618
619 let d1: Decimal = Decimal::new(123456789, -2);
620 let d10: Decimal = Decimal::new(123456789, -1);
621 let d100: Decimal = Decimal::new(123456789, 0);
622
623 let (diff_integer, diff_fractional) = Decimal::difference_by_parts_lossy(&d1, &d1);
625 assert_eq!(diff_integer, 0.into());
627 assert_eq!(diff_fractional, 0.0);
629
630 let (diff_integer, diff_fractional) = Decimal::difference_by_parts_lossy(&d10, &d1);
632 assert_eq!(diff_integer, 11111111.into());
634 let expected = 0.01;
636 let relative_error = ((diff_fractional - expected) / expected).abs();
639 assert!(relative_error <= 10.0 * f64::EPSILON);
640
641 let (diff_integer, diff_fractional) = Decimal::difference_by_parts_lossy(&d100, &d1);
643 assert_eq!(diff_integer, 122222222.into());
645 let expected = -0.89;
647 let relative_error = ((diff_fractional - expected) / expected).abs();
650 assert!(relative_error <= 10.0 * f64::EPSILON);
651
652 let (diff_integer, diff_fractional) = Decimal::difference_by_parts_lossy(
654 &Decimal::new(10_000_002, -7),
655 &Decimal::new(12, -1),
656 );
657 assert_eq!(diff_integer, 0.into());
659 let expected = -0.1999998;
661 let relative_error = ((diff_fractional - expected) / expected).abs();
664 assert!(relative_error <= 10.0 * f64::EPSILON);
665 }
666
667 #[test]
668 fn test_decimal_try_from_large_f64_ok() {
669 let actual: Decimal = 1234567.89f64.try_into().unwrap();
670 let expected = Decimal::new(123456789, -2);
671
672 let (diff_int, diff_fract) = Decimal::difference_by_parts_lossy(&actual, &expected);
673 assert_eq!(diff_int, 0.into(), "integer component expected equal");
674 assert!(diff_fract < 100_000.into()); let actual: Decimal = f64::MAX.try_into().unwrap();
678 let expected = Decimal::new(0, 0);
679
680 let (diff_int, diff_fract) = Decimal::difference_by_parts_lossy(&actual, &expected);
681 assert_eq!(
682 UInt::from(diff_int.magnitude().clone()).number_of_decimal_digits(),
683 309,
684 "f64::MAX should have 309 decimal digits"
685 );
686 assert_eq!(diff_fract, 0.into());
687
688 let actual: Decimal = f64::MIN.try_into().unwrap();
690 let expected = Decimal::new(0, 0);
691
692 let (diff_int, diff_fract) = Decimal::difference_by_parts_lossy(&actual, &expected);
693 assert_eq!(
694 UInt::from(diff_int.magnitude().clone()).number_of_decimal_digits(),
695 309,
696 "f64::MIN should have 309 decimal digits"
697 );
698 assert_eq!(diff_fract, 0.into());
699 }
700
701 #[test]
702 fn test_decimal_try_from_very_small_f64_ok() {
703 let actual: Decimal = 0.000_000_000_000_000_1.try_into().unwrap();
704 let expected = Decimal::new(1, -16);
705
706 let (diff_int, diff_fract) = Decimal::difference_by_parts_lossy(&actual, &expected);
707 assert_eq!(
708 UInt::from(diff_int.magnitude().clone()).number_of_decimal_digits(),
709 1
710 );
711 assert_eq!(diff_fract, 0.into());
712
713 let actual: Decimal = f64::MIN_POSITIVE.try_into().unwrap();
715 let expected = Decimal::new(2, -308);
716
717 let (diff_int, diff_fract) = Decimal::difference_by_parts_lossy(&actual, &expected);
718 assert_eq!(
719 UInt::from(diff_int.magnitude().clone()).number_of_decimal_digits(),
720 1
721 );
722 assert_eq!(diff_fract, 0.into());
723 }
724
725 #[rstest]
726 #[case::positive_infinity(f64::infinity())]
727 #[case::negative_infinity(f64::neg_infinity())]
728 #[case::nan(f64::nan())]
729 fn test_decimal_try_from_f64_err(#[case] value: f64) {
730 let conversion_result: IonResult<Decimal> = value.try_into();
731 assert!(conversion_result.is_err());
732 }
733
734 #[test]
735 fn test_convert_to_big_decimal() {
736 let decimal = Decimal::new(-24601, -3);
737 let big_decimal: BigDecimal = decimal.try_into().unwrap();
738 let double = big_decimal.to_f64().unwrap();
739 assert_eq!(-24.601, double);
740
741 let decimal = Decimal::negative_zero();
744 let conversion_result: IonResult<BigDecimal> = decimal.try_into();
745 assert!(conversion_result.is_err());
746
747 let decimal = Decimal::negative_zero_with_exponent(6);
748 let conversion_result: IonResult<BigDecimal> = decimal.try_into();
749 assert!(conversion_result.is_err());
750
751 let decimal = Decimal::negative_zero_with_exponent(-6);
752 let conversion_result: IonResult<BigDecimal> = decimal.try_into();
753 assert!(conversion_result.is_err());
754 }
755
756 #[test]
757 fn test_convert_from_big_decimal() {
758 let big_decimal: BigDecimal = BigDecimal::new((-24601).into(), 3);
759 let actual: Decimal = big_decimal.into();
760 let expected = Decimal::new(-24601, -3);
761 assert_eq!(actual, expected);
762 }
763
764 #[rstest]
765 #[case(Decimal::new(-24601, -3), 3)]
766 #[case(Decimal::new(u64::MAX, -5), 5)]
767 #[case(Decimal::new(u64::MAX, 0), 0)]
768 #[case(Decimal::new(4, 3), -3)]
769 fn test_scale(#[case] value: Decimal, #[case] expected: i64) {
770 assert_eq!(value.scale(), expected)
771 }
772
773 #[rstest]
774 #[case(Decimal::new(-24601, -3), 5)]
775 #[case(Decimal::new(5, -3), 1)]
776 #[case(Decimal::new(24, -5), 2)]
777 #[case(Decimal::new(0, 0), 1)]
778 #[case(Decimal::new(234, 0), 3)]
779 #[case(Decimal::new(-234, 2), 5)]
780 #[case(Decimal::new(BigUint::from(u64::MAX), 3), 23)]
781 #[case(Decimal::new(BigUint::from(u128::MAX), -2), 39)]
782 fn test_precision(#[case] value: Decimal, #[case] expected: u64) {
783 assert_eq!(value.precision(), expected);
784 }
785}