ion_rs/types/
decimal.rs

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/// An arbitrary-precision Decimal type with a distinct representation of negative zero (`-0`).
16#[derive(Clone, Debug)]
17pub struct Decimal {
18    // A Coefficient is a Sign/UInteger pair supporting integers of arbitrary size
19    pub(crate) coefficient: Coefficient,
20    pub(crate) exponent: i64,
21}
22
23impl Decimal {
24    /// Constructs a new Decimal with the provided components. The value of the decimal is
25    ///    (coefficient * 10^exponent) * (if sign == Sign::Negative { -1 } else { 1 })
26    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    /// Returns scale of the Decimal value
35    /// If zero or positive, a scale indicates the number of digits to the right of the decimal point.
36    /// If negative, the unscaled value of the number is multiplied by ten to the power of the negation of the scale.
37    /// For example, a scale of -3 means the unscaled value is multiplied by 1000.
38    pub fn scale(&self) -> i64 {
39        self.exponent.neg()
40    }
41
42    /// Returns the number of digits in the non-scaled integer representation of the decimal.
43    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    /// Constructs a Decimal with the value `-0d0`. This is provided as a convenience method
51    /// because Rust will ignore a unary minus when it is applied to an zero literal (`-0`).
52    pub fn negative_zero() -> Decimal {
53        Decimal::negative_zero_with_exponent(0)
54    }
55
56    /// Constructs a Decimal with a coefficient of `-0` and the specified exponent. This function
57    /// is provided as a convenience method because Rust will ignore a unary minus when it is
58    /// applied to a zero literal (`-0`).
59    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    /// Returns `true` if this Decimal is a zero of any sign or exponent.
68    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    /// Returns true if this Decimal's coefficient has a negative sign AND a magnitude greater than
77    /// zero. Otherwise, returns false. (Negative zero returns false.)
78    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    /// Semantically identical to `self >= Decimal::new(1, 0)`, but much cheaper to compute.
87    pub(crate) fn is_greater_than_or_equal_to_one(&self) -> bool {
88        // If the coefficient has a magnitude of zero, the Decimal is a zero of some precision
89        // and so is not >= 1.
90        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 the coefficient is non-zero, look at the exponent. A positive exponent means the
97        // value has to be >= 1.
98        if self.exponent >= 0 {
99            return true;
100        }
101
102        // If the exponent is negative, we have to see whether if its magnitude outweighs the
103        // magnitude of the coefficient.
104        let num_coefficient_decimal_digits = self.coefficient.number_of_decimal_digits();
105        num_coefficient_decimal_digits > self.exponent.unsigned_abs()
106    }
107
108    // Determines whether the first decimal value is greater than, equal to, or less than
109    // the second decimal value.
110    fn compare(d1: &Decimal, d2: &Decimal) -> Ordering {
111        if d1.is_zero() && d2.is_zero() {
112            // Ignore the sign/exponent if they're both some flavor of zero.
113            return Ordering::Equal;
114        }
115        // Even if the exponents are wildly different, disagreement in the coefficient's signs
116        // still tells us which value is bigger. (This approach causes `-0` to be considered less
117        // than `0`; see the to-do comment on this method.)
118        let sign_cmp = d1.coefficient.sign().cmp(&d2.coefficient.sign());
119        if sign_cmp != Ordering::Equal {
120            return sign_cmp;
121        }
122
123        // If the signs are the same, compare their magnitudes.
124        let ordering = Decimal::compare_magnitudes(d1, d2);
125
126        if d1.coefficient.sign() == Sign::Positive {
127            // If the values are both positive, use the magnitudes' ordering.
128            ordering
129        } else {
130            // If the values are both negative, reverse the magnitudes' ordering.
131            // For example: -100 has a greater magnitude (i.e. absolute value) than -99,
132            //              but -99 is the larger number.
133            ordering.reverse()
134        }
135    }
136
137    // Compare the magnitudes (absolute values) of the provided decimal values.
138    fn compare_magnitudes(d1: &Decimal, d2: &Decimal) -> Ordering {
139        // If the exponents match, we can compare the two coefficients directly.
140        if d1.exponent == d2.exponent {
141            return d1.coefficient.magnitude().cmp(d2.coefficient.magnitude());
142        }
143
144        // If the exponents don't match, we need to scale one of the magnitudes to match the other
145        // for comparison. For example, when comparing 16e3 and 1600e1, we can't compare the
146        // magnitudes (16 and 1600) directly. Instead, we need to multiply 16 by 10^2 to compensate
147        // for the difference in their exponents (3-1). Then we'll be comparing 1600 to 1600,
148        // and can safely conclude that they are equal.
149        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    // Scales up the coefficient associated with a greater exponent and compares it with the
157    // other coefficient. `d1` must have a larger exponent than `d2`.
158    fn compare_scaled_coefficients(d1: &Decimal, d2: &Decimal) -> Ordering {
159        let exponent_delta = d1.exponent - d2.exponent;
160        // d1 has a larger exponent, so scale up its coefficient to match d2's exponent.
161        // For example, when comparing these values of d1 and d2:
162        //     d1 =  8 * 10^3
163        //     d2 = 80 * 10^2
164        // d1 has the larger exponent (3). We need to scale its coefficient up to d2's 10^2 scale.
165        // We do this by multiplying it times 10^exponent_delta, which is 1 in this case.
166        // This lets us compare 80 and 80, determining that the decimals are equal.
167        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    /// Extract the integer and fractional parts and the exponents of this as a `(BigInt, (BigInt, i64))`
173    ///
174    /// ```ignore
175    /// # use num_bigint::BigInt;
176    /// # use ion_rs::Decimal;
177    /// let d1: Decimal = Decimal::new(123456789, -2);
178    /// let (int_part, (frac_part, exponent)) = d1.into_parts();
179    /// assert_eq!(int_part, BigInt::from(1234567u64));
180    /// assert_eq!(frac_part, BigInt::from(89));
181    /// assert_eq!(exponent, -2);
182    /// ```
183    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    /// Extract the integer and fractional parts of this as a `(BigInt, f64)`.
198    /// Some precision is lost in the conversion of the fractional part to an `f64`.
199    /// See also [`Self::into_parts`]
200    ///
201    /// ```ignore
202    /// # use num_bigint::BigInt;
203    /// # use ion_rs::Decimal;
204    /// let d1: Decimal = Decimal::new(123456789, -2);
205    /// let (int_part, frac_part) = d1.into_parts_lossy();
206    /// assert_eq!(int_part, BigInt::from(1234567u64));
207    /// assert_eq!(frac_part, 0.89);
208    /// ```
209    pub(crate) fn into_parts_lossy(self) -> (BigInt, f64) {
210        // turn a fractional part and exponent (e.g., `123` & -3) into an f64 less than `0` (e.g., `0.123`)
211        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    /// Returns ([`num_bigint::BigInt`], [`f64`]) representing the differences
223    /// between integer and fractional components of d1 & d2 respectively.
224    ///
225    /// This is largely useful to compare two [`Decimal`] values without the full suite of mathematical
226    /// and comparison operations.
227    ///
228    /// ```ignore
229    /// # use num_bigint::BigInt;
230    /// # use ion_rs::Decimal;
231    /// let (diff_integer, diff_fractional) = Decimal::difference_by_parts_lossy(Decimal::new(123, 0), Decimal::new(12, 1));
232    /// assert_eq!(diff_integer, BigInt::from(3));
233    /// assert_eq!(diff_fractional, 0.0);
234    ///
235    /// let (diff_integer, diff_fractional) = Decimal::difference_by_parts_lossy(Decimal::new(12345, -2), Decimal::new(123, 0));
236    /// assert_eq!(diff_integer, BigInt::from(0));
237    /// assert_eq!(diff_fractional, 0.45);
238    /// ```
239    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    // Numerical order (least to greatest) and then by number of significant figures (least to greatest)
263    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        // If the signs are the same, compare their magnitudes.
270        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        // Finally, compare the number of significant figures.
278        // Since we know the numeric value is the same, we only need to look at the exponents here.
279        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                // Discard the sign and convert the value to a u64.
312                let magnitude: u64 = value.checked_abs()
313                        .and_then(|v| Some(v.abs() as u64))
314                        // If .abs() fails, it's because <$t>::MIN.abs() cannot be represented
315                        // as a $t. We can handle this case by simply using <$>::MAX + 1
316                        .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        // Defer to the f64 implementation of `TryInto`
330        (value as f64).try_into()
331    }
332}
333
334impl TryFrom<f64> for Decimal {
335    type Error = IonError;
336    /// Attempts to create a Decimal from an f64. Returns an Error if the f64 being
337    /// converted is a special value, including:
338    ///   * Infinity
339    ///   * Negative infinity
340    ///   * NaN (not-a-number)
341    /// Otherwise, returns Ok.
342    ///
343    /// Because Decimal can represent negative zero, f64::neg_zero() IS supported.
344    ///
345    /// NOTE: While the resulting decimal will be a very close approximation of the original f64's
346    ///       value, this is an inherently lossy operation. Floating point values do not encode a
347    ///       precision. When converting an f64 to a Decimal, a precision for the new Decimal must
348    ///       be chosen somewhat arbitrarily. Do NOT rely on the precision of the resulting Decimal.
349    ///       This implementation may change without notice.
350    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        // You can't use the `log10` operation on a zero value, so check for these cases explicitly.
362        if value == 0f64 {
363            //    ^- Positive and negative zero are mathematically equivalent,
364            //       so we can use `==` here to check for both.
365            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            // prefer a compact representation for the coefficient; fallback to bigint
373            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 the f64 is an integer value, we can convert it to a decimal trivially.
388        // The `fract()` method returns the fractional part of the value.
389        // If fract() is zero, then `value` is an integer.
390        if value.fract().is_zero() {
391            try_convert(value, 0)
392        } else {
393            // If the f64 is not a round number, attempt to preserve as many decimal places of precision
394            // as possible.
395
396            // f64::DIGITS is the number of base 10 digits of fractional precision in an f64: 15
397            const PRECISION: u32 = f64::DIGITS;
398
399            // For `value.abs() >= 1` -> 0
400            // Else -> number of decimal `0` before the first non-`0` after the decimal point
401            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] // https://github.com/rust-lang/rustfmt/issues/3255
413    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
414        // Inspired by the formatting conventions of Java's BigDecimal.toString()
415        const WIDE_NUMBER: usize = 6; // if you think about it, six is a lot 🙃
416
417        let digits = &*self.coefficient.magnitude.to_string();
418        let len = digits.len();
419        // The index of the decimal point, relative to the magnitude representation
420        //       0123                                                       01234
421        // Given ABCDd-2, the decimal gets inserted at position 2, yielding AB.CD
422        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 { // e.g. A.BCDEFGd6
429            write!(f, "{}.{}d{}", &digits[0..1], &digits[1..len], (dot_index - 1))
430        } else if self.exponent == 0 { // e.g. ABC.
431            write!(f, "{}.", &digits)
432        } else if self.exponent >= 0 { // e.g. ABCd1
433            write!(f, "{}d{}", &digits, self.exponent)
434        } else { // exponent < 0, there is a fractional component
435            if dot_index > 0 { // e.g. A.BC or AB.C
436                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) { // e.g. 0.ABC or 0.000ABC
439                let width = dot_index.unsigned_abs() as usize + len;
440                write!(f, "0.{digits:0>width$}", width = width, digits = digits)
441            } else { // e.g. A.BCd-12
442                write!(f, "{}.{}d{}", &digits[0..1], &digits[1..len], (dot_index - 1))
443            }
444        }
445    }
446}
447
448/// Make a Decimal from a BigDecimal. This is a lossless operation.
449impl 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        // Discard the BigInt coefficient's sign before converting it to a BigUint to ensure
458        // the conversion succeeds.
459        let magnitude: BigUint = big_int_coefficient.abs().to_biguint().unwrap();
460        // From the BigInt docs: "Note that a positive exponent indicates a negative power of 10."
461        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    /// Attempts to create a BigDecimal from a Decimal. Returns an Error if the Decimal being
470    /// converted is a negative zero, which BigDecimal cannot represent. Returns Ok otherwise.
471    fn try_from(value: Decimal) -> Result<Self, Self::Error> {
472        // The Coefficient type cannot be converted to a BigInt if it is a negative zero.
473        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        // Decimal zeros of any sign/exponent are mathematically equal.
513        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        // To be IonEq, decimal zeros must have the same sign and exponent.
527        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    // Each tuple is a coefficient/exponent pair that will be used to construct a Decimal.
535    // The boolean indicates whether the two Decimals are expected to be equal.
536    #[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    // Each tuple is a coefficient/exponent pair that will be used to construct a Decimal.
553    // The boolean indicates whether the two Decimals are expected to be Ion-equal.
554    #[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    // Each tuple is a coefficient/exponent pair that will be used to construct a Decimal
575    #[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        // Make sure the inverse relationship holds
593        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        // diff d1 with d1
624        let (diff_integer, diff_fractional) = Decimal::difference_by_parts_lossy(&d1, &d1);
625        // 1234567 - 1234567 -> 0
626        assert_eq!(diff_integer, 0.into());
627        // 89 - 89 -> 0
628        assert_eq!(diff_fractional, 0.0);
629
630        // diff d10 with d1
631        let (diff_integer, diff_fractional) = Decimal::difference_by_parts_lossy(&d10, &d1);
632        // 12345678 - 1234567 -> 11111111
633        assert_eq!(diff_integer, 11111111.into());
634        // .9 - .89 -> .01
635        let expected = 0.01;
636        // ideally, this would be based on ULPs, not epsilon
637        //  Cf. https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
638        let relative_error = ((diff_fractional - expected) / expected).abs();
639        assert!(relative_error <= 10.0 * f64::EPSILON);
640
641        // diff d100 with d1
642        let (diff_integer, diff_fractional) = Decimal::difference_by_parts_lossy(&d100, &d1);
643        // 123456789 - 1234567 -> 122222222
644        assert_eq!(diff_integer, 122222222.into());
645        // .0 - .89 -> -.89
646        let expected = -0.89;
647        // ideally, this would be based on ULPs, not epsilon
648        //  Cf. https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
649        let relative_error = ((diff_fractional - expected) / expected).abs();
650        assert!(relative_error <= 10.0 * f64::EPSILON);
651
652        // diff 1.000_000_2 with 1.2
653        let (diff_integer, diff_fractional) = Decimal::difference_by_parts_lossy(
654            &Decimal::new(10_000_002, -7),
655            &Decimal::new(12, -1),
656        );
657        // 1 - 1 -> 0
658        assert_eq!(diff_integer, 0.into());
659        // 0.0000002 - 0.2 -> -0.1999998
660        let expected = -0.1999998;
661        // ideally, this would be based on ULPs, not epsilon
662        //  Cf. https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
663        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()); // 100,000 arbitrarily chosen as 1/3 of the 15 decimal digits of precision
675
676        // MAX f64 - e.g., 1.7976931348623157e+308_f64
677        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        // MIN f64 - e.g., -1.7976931348623157e+308_f64
689        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        // MIN_POSITIVE f64 - e.g., 2.2250738585072014e-308_f64
714        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        // Any form of negative zero will fail to be converted.
742
743        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}