ion_rs/types/
coefficient.rs

1use num_bigint::{BigInt, BigUint};
2use num_traits::Zero;
3
4use crate::result::{illegal_operation, IonError};
5use crate::types::UInt;
6use std::convert::TryFrom;
7use std::fmt::{Display, Formatter};
8use std::ops::{MulAssign, Neg};
9
10/// Indicates whether the Coefficient's magnitude is less than 0 (negative) or not (positive).
11/// When the magnitude is zero, the Sign can be used to distinguish between -0 and 0.
12#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
13pub enum Sign {
14    Negative,
15    Positive,
16}
17
18/// A signed integer that can be used as the coefficient of a Decimal value. This type does not
19/// consider `0` and `-0` to be equal and supports magnitudes of arbitrary size.
20// These trait derivations rely closely on the manual implementations of PartialEq and Ord on
21// [Magnitude].
22#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
23pub struct Coefficient {
24    pub(crate) sign: Sign,
25    pub(crate) magnitude: UInt,
26}
27
28impl Coefficient {
29    pub(crate) fn new<I: Into<UInt>>(sign: Sign, magnitude: I) -> Self {
30        let magnitude = magnitude.into();
31        Coefficient { sign, magnitude }
32    }
33
34    pub(crate) fn sign(&self) -> Sign {
35        self.sign
36    }
37
38    pub(crate) fn magnitude(&self) -> &UInt {
39        &self.magnitude
40    }
41
42    /// Returns the number of digits in the base-10 representation of the coefficient
43    pub(crate) fn number_of_decimal_digits(&self) -> u64 {
44        self.magnitude.number_of_decimal_digits()
45    }
46
47    /// Constructs a new Coefficient that represents negative zero.
48    pub(crate) fn negative_zero() -> Self {
49        Coefficient {
50            sign: Sign::Negative,
51            magnitude: UInt::U64(0),
52        }
53    }
54
55    /// Returns true if the Coefficient represents positive zero.
56    pub(crate) fn is_negative_zero(&self) -> bool {
57        match (self.sign, &self.magnitude) {
58            (Sign::Negative, UInt::U64(0)) => true,
59            (Sign::Negative, UInt::BigUInt(b)) if b.is_zero() => true,
60            _ => false,
61        }
62    }
63
64    /// Returns true if the Coefficient represents positive zero.
65    pub(crate) fn is_positive_zero(&self) -> bool {
66        match (self.sign, &self.magnitude) {
67            (Sign::Positive, UInt::U64(0)) => true,
68            (Sign::Positive, UInt::BigUInt(b)) if b.is_zero() => true,
69            _ => false,
70        }
71    }
72
73    /// Returns true if the Coefficient represents a zero of any sign.
74    pub(crate) fn is_zero(&self) -> bool {
75        match (self.sign, &self.magnitude) {
76            (_, UInt::U64(0)) => true,
77            (_, UInt::BigUInt(b)) if b.is_zero() => true,
78            _ => false,
79        }
80    }
81
82    /// If the value can fit in an i64, return it as such. This is useful for
83    /// inline representations.
84    pub(crate) fn as_i64(&self) -> Option<i64> {
85        match self.magnitude {
86            UInt::U64(unsigned) => match i64::try_from(unsigned) {
87                Ok(signed) => match self.sign {
88                    Sign::Negative => Some(signed.neg()), // cannot overflow (never `MIN`)
89                    Sign::Positive => Some(signed),
90                },
91                Err(_) => None,
92            },
93            UInt::BigUInt(_) => None,
94        }
95    }
96}
97
98// This macro makes it possible to turn unsigned integers into a Coefficient using `.into()`.
99macro_rules! impl_coefficient_from_unsigned_int_types {
100    ($($t:ty),*) => ($(
101        impl From<$t> for Coefficient {
102            fn from(value: $t) -> Coefficient {
103                Coefficient::new(Sign::Positive, value)
104            }
105        }
106    )*)
107}
108impl_coefficient_from_unsigned_int_types!(u8, u16, u32, u64, u128, usize, BigUint);
109
110// This macro makes it possible to turn signed integers into a Coefficient using `.into()`.
111macro_rules! impl_coefficient_from_signed_int_types {
112    ($($t:ty),*) => ($(
113        impl From<$t> for Coefficient {
114            fn from(value: $t) -> Coefficient {
115                let sign = if value < <$t>::zero() { Sign::Negative } else { Sign::Positive };
116                Coefficient::new(sign, value)
117            }
118        }
119    )*)
120}
121impl_coefficient_from_signed_int_types!(i8, i16, i32, i64, i128, isize);
122
123// `BigInt` can't represent -0, so this is technically a lossy operation.
124impl TryFrom<Coefficient> for BigInt {
125    type Error = IonError;
126
127    /// Attempts to create a BigInt from a Coefficient. Returns an Error if the Coefficient being
128    /// converted is a negative zero, which BigInt cannot represent. Returns Ok otherwise.
129    fn try_from(value: Coefficient) -> Result<Self, Self::Error> {
130        if value.is_negative_zero() {
131            illegal_operation("Cannot convert negative zero Decimal to BigInt")?;
132        }
133        let mut big_int: BigInt = match value.magnitude {
134            UInt::U64(m) => m.into(),
135            UInt::BigUInt(m) => m.into(),
136        };
137        if value.sign == Sign::Negative {
138            big_int.mul_assign(-1);
139        }
140        Ok(big_int)
141    }
142}
143
144impl TryFrom<BigInt> for Coefficient {
145    type Error = IonError;
146
147    fn try_from(value: BigInt) -> Result<Self, Self::Error> {
148        let (sign, magnitude) = value.into_parts();
149        let sign = match sign {
150            num_bigint::Sign::Minus => Sign::Negative,
151            num_bigint::Sign::Plus => Sign::Positive,
152            num_bigint::Sign::NoSign => {
153                if magnitude.is_zero() {
154                    Sign::Positive
155                } else {
156                    return illegal_operation(
157                        "Cannot convert sign-less non-zero BigInt to Decimal.",
158                    );
159                }
160            }
161        };
162        Ok(Coefficient::new(sign, magnitude))
163    }
164}
165
166impl Display for Coefficient {
167    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
168        match self.sign {
169            Sign::Positive => {}
170            Sign::Negative => write!(f, "-")?,
171        };
172        match &self.magnitude {
173            UInt::U64(m) => write!(f, "{}", *m),
174            UInt::BigUInt(m) => write!(f, "{m}"),
175        }
176    }
177}
178
179#[cfg(test)]
180mod coefficient_tests {
181    use crate::ion_data::IonEq;
182    use num_bigint::BigUint;
183
184    use crate::types::{Coefficient, Decimal};
185
186    fn eq_test<I1, I2>(c1: I1, c2: I2)
187    where
188        I1: Into<Coefficient>,
189        I2: Into<Coefficient>,
190    {
191        let c1 = c1.into();
192        let c2 = c2.into();
193        assert_eq!(c1, c2);
194    }
195
196    #[test]
197    fn test_coefficient_eq() {
198        eq_test(0u64, 0u64);
199        eq_test(0u64, BigUint::from(0u64));
200        eq_test(BigUint::from(0u64), 0u64);
201        eq_test(BigUint::from(0u64), BigUint::from(0u64));
202
203        eq_test(u64::MAX, u64::MAX);
204        eq_test(u64::MAX, BigUint::from(u64::MAX));
205        eq_test(BigUint::from(u64::MAX), u64::MAX);
206        eq_test(BigUint::from(u64::MAX), BigUint::from(u64::MAX));
207
208        eq_test(BigUint::from(u128::MAX), BigUint::from(u128::MAX));
209    }
210
211    #[test]
212    fn test_negative_zero_eq() {
213        let neg_zero = Decimal::new(Coefficient::negative_zero(), 0);
214        let pos_zero = Decimal::new(0, 0);
215        assert_eq!(neg_zero, neg_zero);
216        assert!(neg_zero.ion_eq(&neg_zero));
217
218        assert_eq!(neg_zero, pos_zero);
219        assert!(!neg_zero.ion_eq(&pos_zero));
220
221        assert_eq!(pos_zero, pos_zero);
222        assert!(pos_zero.ion_eq(&pos_zero));
223    }
224}