Skip to main content

ion_rs/types/decimal/
coefficient.rs

1//! A representation of a decimal value's coefficient.
2
3use std::convert::TryFrom;
4use std::fmt::{Display, Formatter};
5
6use crate::result::{IonError, IonFailure};
7use crate::types::CountDecimalDigits;
8use crate::IonResult;
9use crate::{Int, UInt};
10
11/// Indicates whether the `Coefficient`'s magnitude is less than 0 (negative) or not (positive).
12/// When the magnitude is zero, the `Sign` can be used to distinguish between -0 and 0.
13#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
14pub enum Sign {
15    Negative = -1,
16    Positive = 1,
17}
18
19/// A signed integer that can be used as the coefficient of a [`Decimal`](crate::Decimal) value.
20///
21/// Unlike `Int`, this type preserves the distinction between `0` and `-0`. When tested for mathematical
22/// equality using [`PartialEq::eq`], [`Coefficient::ZERO`] and [`Coefficient::NEGATIVE_ZERO`] will be
23/// considered equal. When tested for Ion data equality using [`IonData::eq`](crate::IonData::eq),
24/// they will be considered unequal.
25///
26/// While the Ion specification allows this type to be of arbitrary size, this implementation currently
27/// supports coefficients in the integer range supported by `i128`.
28#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
29pub struct Coefficient {
30    /// This field exists solely to preserve the distinction between `0` and `-0`.
31    /// It will agree with the sign information in the `magnitude` field in all cases *except*
32    /// when the coefficient is `-0`.
33    sign: Sign,
34    magnitude: Int,
35}
36
37impl Coefficient {
38    pub const ZERO: Coefficient = Coefficient {
39        sign: Sign::Positive,
40        magnitude: Int::ZERO,
41    };
42
43    pub const NEGATIVE_ZERO: Coefficient = Coefficient {
44        sign: Sign::Negative,
45        magnitude: Int::ZERO,
46    };
47
48    pub(crate) fn new<I: Into<Int>>(value: I) -> Self {
49        let value: Int = value.into();
50        let sign = if value.is_negative() {
51            Sign::Negative
52        } else {
53            Sign::Positive
54        };
55        Coefficient {
56            sign,
57            magnitude: value,
58        }
59    }
60
61    pub(crate) fn from_sign_and_value(sign: Sign, magnitude: impl Into<Int>) -> Self {
62        Self {
63            sign,
64            magnitude: magnitude.into(),
65        }
66    }
67
68    pub fn sign(&self) -> Sign {
69        self.sign
70    }
71
72    pub fn magnitude(&self) -> UInt {
73        self.magnitude.unsigned_abs()
74    }
75
76    pub fn is_negative(&self) -> bool {
77        self.sign == Sign::Negative
78    }
79
80    /// Returns the number of digits in the base-10 representation of the coefficient
81    pub(crate) fn number_of_decimal_digits(&self) -> u32 {
82        self.magnitude.clone().count_decimal_digits()
83    }
84
85    /// Constructs a new Coefficient that represents negative zero.
86    pub(crate) fn negative_zero() -> Self {
87        Coefficient {
88            sign: Sign::Negative,
89            magnitude: 0u64.into(),
90        }
91    }
92
93    /// Returns true if the Coefficient represents negative zero.
94    pub fn is_negative_zero(&self) -> bool {
95        self.is_zero_with_sign(Sign::Negative)
96    }
97
98    /// Returns true if the Coefficient represents positive zero.
99    pub fn is_positive_zero(&self) -> bool {
100        self.is_zero_with_sign(Sign::Positive)
101    }
102
103    pub(crate) fn is_zero_with_sign(&self, test_sign: Sign) -> bool {
104        self.sign == test_sign && self.magnitude.is_zero()
105    }
106
107    /// Returns true if the Coefficient represents a zero of any sign.
108    pub fn is_zero(&self) -> bool {
109        self.magnitude().is_zero()
110    }
111
112    /// Returns the coefficient as an `Int`.
113    /// If the coefficient is negative zero, returns `None`.
114    pub(crate) fn as_int(&self) -> Option<Int> {
115        if self.is_negative_zero() {
116            // Returning an unsigned zero would be lossy.
117            return None;
118        }
119        Some(self.magnitude.clone())
120    }
121}
122
123// This macro makes it possible to turn unsigned integers into a Coefficient using `.into()`.
124macro_rules! impl_coefficient_from_unsigned_int_types {
125    ($($t:ty),*) => ($(
126        impl From<$t> for Coefficient {
127            fn from(value: $t) -> Coefficient {
128                Coefficient::new(value)
129            }
130        }
131    )*)
132}
133impl_coefficient_from_unsigned_int_types!(u8, u16, u32, u64, u128, usize, UInt);
134
135// This macro makes it possible to turn signed integers into a Coefficient using `.into()`.
136macro_rules! impl_coefficient_from_signed_int_types {
137    ($($t:ty),*) => ($(
138        impl From<$t> for Coefficient {
139            fn from(value: $t) -> Coefficient {
140                Coefficient::new(value)
141            }
142        }
143    )*)
144}
145impl_coefficient_from_signed_int_types!(i8, i16, i32, i64, i128, isize, Int);
146
147impl TryFrom<Coefficient> for Int {
148    type Error = IonError;
149
150    fn try_from(value: Coefficient) -> Result<Self, Self::Error> {
151        if value.is_negative_zero() {
152            return IonResult::illegal_operation("cannot convert negative zero Coefficient to Int");
153        }
154        Ok(value.magnitude)
155    }
156}
157
158impl TryFrom<&Coefficient> for Int {
159    type Error = IonError;
160
161    fn try_from(value: &Coefficient) -> Result<Self, Self::Error> {
162        value.clone().try_into()
163    }
164}
165
166impl TryFrom<Coefficient> for UInt {
167    type Error = IonError;
168
169    fn try_from(value: Coefficient) -> Result<Self, Self::Error> {
170        if value.is_negative() {
171            return IonResult::illegal_operation("cannot convert a negative Coefficient to a UInt");
172        }
173        Ok(value.magnitude.unsigned_abs())
174    }
175}
176
177impl TryFrom<&Coefficient> for UInt {
178    type Error = IonError;
179
180    fn try_from(value: &Coefficient) -> Result<Self, Self::Error> {
181        value.clone().try_into()
182    }
183}
184
185impl Display for Coefficient {
186    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
187        match self.sign {
188            Sign::Positive => {}
189            Sign::Negative => write!(f, "-")?,
190        };
191        write!(f, "{}", self.magnitude)
192    }
193}
194
195#[cfg(test)]
196mod coefficient_tests {
197    use crate::ion_data::IonEq;
198    use crate::Int;
199    use crate::{Decimal, UInt};
200
201    use super::*;
202
203    fn eq_test<I1, I2>(c1: I1, c2: I2)
204    where
205        I1: Into<Coefficient>,
206        I2: Into<Coefficient>,
207    {
208        let c1 = c1.into();
209        let c2 = c2.into();
210        assert_eq!(c1, c2);
211    }
212
213    #[test]
214    fn test_coefficient_eq() {
215        eq_test(0u64, 0u64);
216        eq_test(0u64, 0i64);
217        eq_test(0i128, 0u64);
218        eq_test(0i128, 0i64);
219
220        eq_test(u64::MAX, u64::MAX);
221        eq_test(u64::MAX, i128::from(u64::MAX));
222        eq_test(i128::from(u64::MAX), u64::MAX);
223        eq_test(i128::from(u64::MAX), i128::from(u64::MAX));
224
225        eq_test(i128::MAX, i128::MAX);
226    }
227
228    #[test]
229    fn test_negative_zero_eq() {
230        let neg_zero = Decimal::new(Coefficient::negative_zero(), 0);
231        let pos_zero = Decimal::new(0, 0);
232        assert_eq!(neg_zero, neg_zero);
233        assert!(neg_zero.ion_eq(&neg_zero));
234
235        assert_eq!(neg_zero, pos_zero);
236        assert!(!neg_zero.ion_eq(&pos_zero));
237
238        assert_eq!(pos_zero, pos_zero);
239        assert!(pos_zero.ion_eq(&pos_zero));
240    }
241
242    #[test]
243    fn is_negative_zero() {
244        assert!(Coefficient::negative_zero().is_negative_zero());
245        assert!(!Coefficient::new(0).is_negative_zero());
246        assert!(!Coefficient::new(5).is_negative_zero());
247    }
248
249    #[test]
250    fn is_positive_zero() {
251        assert!(Coefficient::new(0).is_positive_zero());
252        assert!(!Coefficient::new(5).is_positive_zero());
253        assert!(!Coefficient::negative_zero().is_positive_zero());
254    }
255
256    #[test]
257    fn is_negative() {
258        assert!(Coefficient::negative_zero().is_negative());
259        assert!(Coefficient::new(-5).is_negative());
260        assert!(!Coefficient::new(5).is_negative());
261    }
262
263    #[test]
264    fn sign() {
265        assert_eq!(Coefficient::negative_zero().sign(), Sign::Negative);
266        assert_eq!(Coefficient::new(0).sign(), Sign::Positive);
267        assert_eq!(Coefficient::new(-5).sign(), Sign::Negative);
268        assert_eq!(Coefficient::new(5).sign(), Sign::Positive);
269    }
270
271    #[test]
272    fn magnitude() {
273        assert_eq!(Coefficient::negative_zero().magnitude(), UInt::from(0u32));
274        assert_eq!(Coefficient::new(0).magnitude(), UInt::from(0u32));
275        assert_eq!(Coefficient::new(-5).magnitude(), UInt::from(5u32));
276        assert_eq!(Coefficient::new(5).magnitude(), UInt::from(5u32));
277    }
278
279    #[test]
280    fn convert_to_int() {
281        // i64
282        assert_eq!(Int::try_from(Coefficient::new(5)), Ok(Int::from(5)));
283        assert_eq!(Int::try_from(Coefficient::new(-5)), Ok(Int::from(-5)));
284
285        let enormous_int = Int::from(12345678901234567890123456789u128);
286        assert_eq!(
287            Int::try_from(Coefficient::new(enormous_int.clone())),
288            Ok(enormous_int.clone())
289        );
290        assert_eq!(
291            Int::try_from(Coefficient::new(enormous_int.clone().neg())),
292            Ok(enormous_int.neg())
293        );
294
295        // Zeros
296        assert_eq!(Int::try_from(Coefficient::new(0)), Ok(Int::from(0)));
297        assert!(Int::try_from(Coefficient::negative_zero()).is_err());
298    }
299
300    #[test]
301    fn test_casting_sign() {
302        assert_eq!(-1, Sign::Negative as i8);
303        assert_eq!(1, Sign::Positive as i8);
304    }
305}