string_number/
lib.rs

1use anyhow::Error;
2#[cfg(any(test, feature = "big_decimal"))]
3use bigdecimal::BigDecimal;
4use std::borrow::Cow;
5use std::cmp::Ordering;
6use std::convert::{TryFrom, TryInto};
7use std::fmt::{Display, Formatter};
8use std::hash::{Hash, Hasher};
9use std::mem::take;
10use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
11
12const INFINITY_STR: &str = "inf";
13const NEG_INFINITY_STR: &str = "-inf";
14const NAN_STR: &str = "NaN";
15const DECIMAL: char = '.';
16const ZERO: &str = "0.0";
17
18/// A decimal number type that stores the number as a string.
19#[derive(Debug, Clone, Eq)]
20pub struct StringNumber(String);
21
22impl Default for StringNumber {
23    fn default() -> Self {
24        Self(ZERO.to_string())
25    }
26}
27
28macro_rules! impl_float_conversion {
29    ($type:ty) => {
30        impl From<$type> for StringNumber {
31            fn from(number: $type) -> Self {
32                let mut s = number.to_string();
33                if !matches!(s.as_str(), NAN_STR | INFINITY_STR | NEG_INFINITY_STR) {
34                    StringNumber::fix_zeros(&mut s);
35                }
36                Self(s)
37            }
38        }
39
40        impl TryFrom<StringNumber> for $type {
41            type Error = Error;
42
43            /// Doesn't return correct NaN value
44            fn try_from(value: StringNumber) -> Result<Self, Self::Error> {
45                value.0.parse().map_err(Error::new)
46            }
47        }
48    };
49}
50
51impl_float_conversion!(f64);
52impl_float_conversion!(f32);
53
54macro_rules! impl_conversion {
55    ($type:ty) => {
56        impl From<$type> for StringNumber {
57            fn from(number: $type) -> Self {
58                let mut s = number.to_string();
59                StringNumber::fix_zeros(&mut s);
60                Self(s)
61            }
62        }
63
64        impl TryFrom<StringNumber> for $type {
65            type Error = Error;
66
67            fn try_from(value: StringNumber) -> Result<Self, Self::Error> {
68                value.0.parse().map_err(Error::new)
69            }
70        }
71    };
72}
73
74impl_conversion!(u64);
75impl_conversion!(i64);
76impl_conversion!(u32);
77impl_conversion!(i32);
78impl_conversion!(u16);
79impl_conversion!(i16);
80impl_conversion!(u8);
81impl_conversion!(i8);
82impl_conversion!(isize);
83impl_conversion!(usize);
84
85#[cfg(any(test, feature = "big_decimal"))]
86impl From<&BigDecimal> for StringNumber {
87    fn from(number: &BigDecimal) -> Self {
88        let mut s = number.to_string();
89        StringNumber::fix_zeros(&mut s);
90        Self(s)
91    }
92}
93
94#[cfg(any(test, feature = "big_decimal"))]
95impl TryFrom<StringNumber> for BigDecimal {
96    type Error = Error;
97
98    fn try_from(value: StringNumber) -> Result<Self, Self::Error> {
99        value.0.parse().map_err(Error::new)
100    }
101}
102
103impl From<PositiveNumber<'_>> for StringNumber {
104    fn from(p: PositiveNumber<'_>) -> Self {
105        StringNumber(p.s.into_owned())
106    }
107}
108
109impl From<PositiveOrNaN<'_>> for StringNumber {
110    fn from(positive_or_nan: PositiveOrNaN<'_>) -> Self {
111        match positive_or_nan {
112            PositiveOrNaN::Positive(p) => p.into(),
113            PositiveOrNaN::NaN => StringNumber::nan(),
114        }
115    }
116}
117
118impl From<NegativeNumber<'_>> for StringNumber {
119    fn from(p: NegativeNumber<'_>) -> Self {
120        StringNumber("-".to_string() + &p.s)
121    }
122}
123
124impl StringNumber {
125    pub fn nan() -> Self {
126        StringNumber(NAN_STR.to_string())
127    }
128
129    pub fn infinity() -> Self {
130        StringNumber(INFINITY_STR.to_string())
131    }
132
133    pub fn neg_infinity() -> Self {
134        StringNumber(NEG_INFINITY_STR.to_string())
135    }
136
137    pub fn is_nan(&self) -> bool {
138        self.0 == NAN_STR
139    }
140
141    pub fn is_infinity(&self) -> bool {
142        self.0 == INFINITY_STR
143    }
144
145    pub fn is_neg_infinity(&self) -> bool {
146        self.0 == NEG_INFINITY_STR
147    }
148
149    fn is_zero(&self) -> bool {
150        matches!(self.0.as_str(), ZERO | "-0.0")
151    }
152
153    fn fix_zeros(s: &mut String) {
154        debug_assert_ne!(s, NAN_STR);
155        debug_assert_ne!(s, INFINITY_STR);
156        debug_assert_ne!(s, NEG_INFINITY_STR);
157        if !s.contains('.') {
158            s.push_str(".0");
159        }
160
161        if s.starts_with("-.") {
162            s.insert(1, '0');
163        } else if s.starts_with('.') {
164            s.insert(0, '0');
165        }
166
167        while s.starts_with('0') && !s.starts_with("0.") {
168            s.remove(0);
169        }
170        while s.starts_with("-0") && !s.starts_with("-0.") {
171            s.remove(1);
172        }
173        while s.ends_with('0') && !s.ends_with(".0") {
174            s.pop();
175        }
176    }
177}
178
179impl Display for StringNumber {
180    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
181        write!(f, "{}", &self.0)
182    }
183}
184
185impl PartialOrd for StringNumber {
186    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
187        if self.is_zero() && other.is_zero() {
188            return Some(Ordering::Equal);
189        }
190
191        let lhs = Number::new(&self.0);
192        let rhs = Number::new(&other.0);
193
194        match lhs {
195            Number::NaN => None,
196            Number::Positive(l) => match rhs {
197                Number::NaN => None,
198                Number::Positive(r) => Some(l.cmp(&r)),
199                Number::Negative(_) => Some(Ordering::Greater),
200            },
201            Number::Negative(l) => match rhs {
202                Number::NaN => None,
203                Number::Positive(_) => Some(Ordering::Less),
204                Number::Negative(r) => Some(match l.positive().cmp(&r.positive()) {
205                    Ordering::Less => Ordering::Greater,
206                    Ordering::Greater => Ordering::Less,
207                    Ordering::Equal => Ordering::Equal,
208                }),
209            },
210        }
211    }
212}
213
214impl PartialEq for StringNumber {
215    fn eq(&self, other: &Self) -> bool {
216        if self.is_zero() && other.is_zero() {
217            true
218        } else {
219            self.0 == other.0
220        }
221    }
222}
223
224impl Hash for StringNumber {
225    fn hash<H: Hasher>(&self, state: &mut H) {
226        if self.is_zero() {
227            ZERO.hash(state);
228        } else {
229            self.0.hash(state);
230        }
231    }
232}
233
234impl Add for StringNumber {
235    type Output = StringNumber;
236
237    fn add(self, rhs: Self) -> Self::Output {
238        let l = Number::new(&self.0);
239        let r = Number::new(&rhs.0);
240
241        match l {
242            Number::NaN => StringNumber::nan(),
243            Number::Positive(l) => match r {
244                Number::NaN => StringNumber::nan(),
245                Number::Positive(r) => (l + r).into(),
246                Number::Negative(r) => (l - r.positive()),
247            },
248            Number::Negative(l) => match r {
249                Number::NaN => StringNumber::nan(),
250                Number::Positive(r) => (r - l.positive()),
251                Number::Negative(r) => (l.positive() + r.positive()).negative().into(),
252            },
253        }
254    }
255}
256
257impl AddAssign for StringNumber {
258    fn add_assign(&mut self, rhs: Self) {
259        *self = take(self) + rhs;
260    }
261}
262
263impl Sub for StringNumber {
264    type Output = StringNumber;
265
266    fn sub(self, rhs: Self) -> Self::Output {
267        let l = Number::new(&self.0);
268        let r = Number::new(&rhs.0);
269
270        match l {
271            Number::NaN => StringNumber::nan(),
272            Number::Positive(l) => match r {
273                Number::NaN => StringNumber::nan(),
274                Number::Positive(r) => (l - r),
275                Number::Negative(r) => (l + r.positive()).into(),
276            },
277            Number::Negative(l) => match r {
278                Number::NaN => StringNumber::nan(),
279                Number::Positive(r) => (l.positive() + r).negative().into(),
280                Number::Negative(r) => r.positive() - l.positive(),
281            },
282        }
283    }
284}
285
286impl SubAssign for StringNumber {
287    fn sub_assign(&mut self, rhs: Self) {
288        *self = take(self) - rhs;
289    }
290}
291
292impl Mul for StringNumber {
293    type Output = StringNumber;
294
295    fn mul(self, rhs: Self) -> Self::Output {
296        let lhs = Number::new(&self.0);
297        let rhs = Number::new(&rhs.0);
298
299        match lhs {
300            Number::NaN => StringNumber::nan(),
301            Number::Positive(l) => match rhs {
302                Number::NaN => StringNumber::nan(),
303                Number::Positive(r) => (l * r).into(),
304                Number::Negative(r) => (l * r.positive()).negative_if_positive(),
305            },
306            Number::Negative(l) => match rhs {
307                Number::NaN => StringNumber::nan(),
308                Number::Positive(r) => (l.positive() * r).negative_if_positive(),
309                Number::Negative(r) => (l.positive() * r.positive()).into(),
310            },
311        }
312    }
313}
314
315impl MulAssign for StringNumber {
316    fn mul_assign(&mut self, rhs: Self) {
317        *self = take(self) * rhs;
318    }
319}
320
321#[derive(Debug)]
322enum Number<'s> {
323    Positive(PositiveNumber<'s>),
324    Negative(NegativeNumber<'s>),
325    NaN,
326}
327
328impl<'s> Number<'s> {
329    fn new(s: &'s str) -> Self {
330        if s == NAN_STR {
331            Number::NaN
332        } else if s.starts_with('-') {
333            Number::Negative(NegativeNumber::new(s))
334        } else {
335            Number::Positive(PositiveNumber::new(s))
336        }
337    }
338}
339
340#[derive(Debug, PartialEq, Eq, Clone)]
341struct PositiveNumber<'s> {
342    s: Cow<'s, str>,
343    // decimal_index >= 1
344    decimal_index: usize,
345}
346
347impl<'s> PositiveNumber<'s> {
348    fn new(s: &'s str) -> Self {
349        Cow::from(s).into()
350    }
351
352    fn infinity() -> PositiveNumber<'s> {
353        PositiveNumber::new(INFINITY_STR)
354    }
355
356    fn is_inf(&self) -> bool {
357        self.s == INFINITY_STR
358    }
359
360    fn is_zero(&self) -> bool {
361        self.s == ZERO
362    }
363
364    fn left_most_index(&self) -> isize {
365        self.decimal_index as isize - 1
366    }
367
368    fn right_most_index(&self) -> isize {
369        -(((self.s.len() - self.decimal_index).saturating_sub(1)) as isize)
370    }
371
372    fn negative(self) -> NegativeNumber<'s> {
373        NegativeNumber {
374            s: self.s,
375            decimal_index: self.decimal_index,
376        }
377    }
378
379    /// greater >= smaller
380    fn subtract_ordered(greater: Self, less: Self) -> PositiveOrNaN<'s> {
381        debug_assert!(greater >= less);
382
383        if greater.is_inf() {
384            return if less.is_inf() {
385                PositiveOrNaN::NaN
386            } else {
387                PositiveNumber::infinity().into()
388            };
389        }
390
391        let mut result_digits: Vec<u8> = Vec::new();
392
393        let mut carry = 0_i8;
394
395        for index in isize::min(greater.right_most_index(), less.right_most_index())
396            ..=isize::max(greater.left_most_index(), less.left_most_index())
397        {
398            let lhs_digit = greater.get_digit(index) as i8;
399            let rhs_digit = less.get_digit(index) as i8;
400
401            let mut digit_difference = lhs_digit - carry - rhs_digit;
402            if digit_difference < 0 {
403                carry = 1;
404                digit_difference += 10;
405            } else {
406                carry = 0;
407            }
408            result_digits.push(digit_difference as u8);
409        }
410
411        PositiveNumber::from(Cow::from(PositiveNumber::digits_to_string(
412            result_digits,
413            usize::max(greater.decimal_index, less.decimal_index),
414        )))
415        .into()
416    }
417
418    fn digits_to_string(mut digits: Vec<u8>, mut decimal_index: usize) -> String {
419        if digits.is_empty() {
420            digits.push(0);
421        }
422
423        let mut bytes: Vec<u8> = Vec::new();
424        if decimal_index == 0 {
425            // bytes starts with '.'
426            bytes.push(b'0');
427            decimal_index += 1;
428        }
429        bytes.extend(digits.iter().rev().copied().map(|n| n + b'0'));
430
431        bytes.insert(decimal_index, b'.');
432        // bytes ends with '.'
433        if decimal_index == bytes.len() - 1 {
434            bytes.push(b'0');
435        }
436
437        String::from_utf8(bytes).unwrap()
438    }
439
440    /// self must not be infinity. n <= 9.
441    fn mul_by_single_digit(&self, n: u8) -> PositiveNumber<'s> {
442        debug_assert!(!self.is_inf());
443        debug_assert!(n <= 9);
444        let mut result_digits: Vec<u8> = Vec::new();
445
446        let mut carry = 0_u8;
447        for index in self.right_most_index()..=self.left_most_index() {
448            let mut digit = self.get_digit(index) * n + carry;
449            carry = digit / 10;
450            digit -= carry * 10;
451            result_digits.push(digit);
452        }
453
454        let mut decimal_index = self.decimal_index;
455        if carry > 0 {
456            result_digits.push(carry);
457            decimal_index += 1;
458        }
459
460        if result_digits.iter().all(|&n| n == 0) {
461            PositiveNumber::default()
462        } else {
463            Cow::from(PositiveNumber::digits_to_string(
464                result_digits,
465                decimal_index,
466            ))
467            .into()
468        }
469    }
470
471    fn mul_10_power(self, power: isize) -> PositiveNumber<'s> {
472        let mut s = self.s.into_owned();
473
474        let decimal_index = s.find(DECIMAL).unwrap();
475        s.remove(decimal_index);
476        let mut new_decimal_index = decimal_index as isize + power;
477        if new_decimal_index <= 0 {
478            s.insert_str(0, &"0".repeat(new_decimal_index.abs() as usize + 1));
479            new_decimal_index = 1;
480        } else if new_decimal_index >= s.len() as isize {
481            s.push_str(&"0".repeat(new_decimal_index.abs() as usize + 1));
482        }
483        s.insert(new_decimal_index.try_into().unwrap(), DECIMAL);
484        StringNumber::fix_zeros(&mut s);
485
486        Cow::from(s).into()
487    }
488}
489
490impl Default for PositiveNumber<'_> {
491    fn default() -> Self {
492        Cow::from(ZERO).into()
493    }
494}
495
496impl<'s> From<Cow<'s, str>> for PositiveNumber<'s> {
497    fn from(s: Cow<'s, str>) -> Self {
498        debug_assert!(s != NAN_STR);
499        debug_assert!(!s.starts_with('-'));
500
501        let decimal_index = if s == INFINITY_STR {
502            0
503        } else {
504            s.find(DECIMAL).unwrap()
505        };
506        Self { s, decimal_index }
507    }
508}
509
510#[cfg(test)]
511impl From<f64> for PositiveNumber<'_> {
512    fn from(f: f64) -> Self {
513        Cow::from(StringNumber::from(f).0).into()
514    }
515}
516
517impl<'s> Add for PositiveNumber<'s> {
518    type Output = PositiveNumber<'s>;
519
520    fn add(self, rhs: Self) -> PositiveNumber<'s> {
521        if self.is_inf() || rhs.is_inf() {
522            return PositiveNumber::infinity();
523        }
524
525        let mut result_digits: Vec<u8> = Vec::new();
526
527        let mut carry = 0_u8;
528
529        for index in isize::min(self.right_most_index(), rhs.right_most_index())
530            ..=isize::max(self.left_most_index(), rhs.left_most_index())
531        {
532            let lhs_digit = self.get_digit(index);
533            let rhs_digit = rhs.get_digit(index);
534
535            let mut digit_sum = lhs_digit + rhs_digit + carry;
536            if digit_sum >= 10 {
537                carry = 1;
538                digit_sum -= 10;
539            } else {
540                carry = 0;
541            }
542            result_digits.push(digit_sum);
543        }
544
545        if carry > 0 {
546            result_digits.push(carry);
547        }
548
549        Cow::from(PositiveNumber::digits_to_string(
550            result_digits,
551            usize::max(self.decimal_index, rhs.decimal_index) + carry as usize,
552        ))
553        .into()
554    }
555}
556
557impl AddAssign for PositiveNumber<'_> {
558    fn add_assign(&mut self, rhs: Self) {
559        *self = take(self) + rhs;
560    }
561}
562
563impl<'s> Sub for PositiveNumber<'s> {
564    type Output = StringNumber;
565
566    fn sub(self, rhs: Self) -> Self::Output {
567        if self > rhs {
568            PositiveNumber::subtract_ordered(self, rhs).into()
569        } else {
570            PositiveNumber::subtract_ordered(rhs, self).negative_if_positive()
571        }
572    }
573}
574
575impl PartialOrd for PositiveNumber<'_> {
576    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
577        Some(self.cmp(other))
578    }
579}
580
581impl Ord for PositiveNumber<'_> {
582    fn cmp(&self, other: &Self) -> Ordering {
583        if self.is_inf() {
584            return if other.is_inf() {
585                Ordering::Equal
586            } else {
587                Ordering::Greater
588            };
589        } else if other.is_inf() {
590            return Ordering::Less;
591        }
592
593        match self.left_most_index().partial_cmp(&other.left_most_index()) {
594            Some(Ordering::Less) => return Ordering::Less,
595            Some(Ordering::Greater) => return Ordering::Greater,
596            _ => {}
597        }
598
599        for index in (self.right_most_index()..=self.left_most_index()).rev() {
600            let lhs_digit = self.get_digit(index);
601            let rhs_digit = other.get_digit(index);
602
603            match lhs_digit.cmp(&rhs_digit) {
604                Ordering::Equal => {}
605                ordering => {
606                    return ordering;
607                }
608            }
609        }
610        Ordering::Equal
611    }
612}
613
614impl GetDigit for PositiveNumber<'_> {
615    fn str(&self) -> &str {
616        &self.s
617    }
618
619    fn decimal_index(&self) -> usize {
620        self.decimal_index
621    }
622}
623
624impl<'s> Mul for PositiveNumber<'s> {
625    type Output = PositiveOrNaN<'s>;
626
627    fn mul(self, rhs: Self) -> Self::Output {
628        if self.is_inf() {
629            return if rhs.is_zero() {
630                PositiveOrNaN::NaN
631            } else {
632                PositiveNumber::infinity().into()
633            };
634        } else if rhs.is_inf() {
635            return if self.is_zero() {
636                PositiveOrNaN::NaN
637            } else {
638                PositiveNumber::infinity().into()
639            };
640        }
641
642        let mut result = PositiveNumber::default();
643        for rhs_index in rhs.right_most_index()..=rhs.left_most_index() {
644            result += self
645                .mul_by_single_digit(rhs.get_digit(rhs_index))
646                .mul_10_power(rhs_index);
647        }
648
649        // Remove extra trailing 0
650        if result.s.ends_with('0') && !result.s.ends_with(".0") {
651            let mut s = result.s.into_owned();
652            s.pop();
653            result.s = s.into();
654        }
655        result.into()
656    }
657}
658
659#[derive(Debug)]
660enum PositiveOrNaN<'s> {
661    Positive(PositiveNumber<'s>),
662    NaN,
663}
664
665impl<'s> PositiveOrNaN<'s> {
666    fn negative_if_positive(self) -> StringNumber {
667        match self {
668            PositiveOrNaN::Positive(p) => p.negative().into(),
669            PositiveOrNaN::NaN => StringNumber::nan(),
670        }
671    }
672}
673
674impl<'s> From<PositiveNumber<'s>> for PositiveOrNaN<'s> {
675    fn from(p: PositiveNumber<'s>) -> Self {
676        PositiveOrNaN::Positive(p)
677    }
678}
679
680#[derive(Debug)]
681struct NegativeNumber<'s> {
682    s: Cow<'s, str>,
683    // decimal_index >= 1
684    decimal_index: usize,
685}
686
687impl<'s> NegativeNumber<'s> {
688    fn new(s: &'s str) -> Self {
689        debug_assert!(s != NAN_STR);
690
691        let stripped = s.strip_prefix('-').unwrap();
692        let decimal_index = if s == NEG_INFINITY_STR {
693            0
694        } else {
695            stripped.find(DECIMAL).unwrap()
696        };
697
698        Self {
699            s: stripped.into(),
700            decimal_index,
701        }
702    }
703
704    fn positive(self) -> PositiveNumber<'s> {
705        PositiveNumber {
706            s: self.s,
707            decimal_index: self.decimal_index,
708        }
709    }
710}
711
712impl GetDigit for NegativeNumber<'_> {
713    fn str(&self) -> &str {
714        &self.s
715    }
716
717    fn decimal_index(&self) -> usize {
718        self.decimal_index
719    }
720}
721
722trait GetDigit {
723    fn str(&self) -> &str;
724
725    fn decimal_index(&self) -> usize;
726
727    /// -1 = 1/10th digit
728    /// 0 = 1s digit
729    /// 1 = 10s digit
730    /// Returns 0 if out of bounds
731    fn get_digit(&self, mut index: isize) -> u8 {
732        if index < 0 {
733            // Skip past decimal point
734            index -= 1;
735        }
736
737        if let Ok(byte_index) = usize::try_from(self.decimal_index() as isize - (index + 1)) {
738            self.str()
739                .as_bytes()
740                .get(byte_index)
741                .map_or(0, |b| b - b'0')
742        } else {
743            0
744        }
745    }
746}
747
748#[cfg(test)]
749mod tests {
750    use super::*;
751    use num_bigint::{BigInt, Sign};
752    use quickcheck::{Arbitrary, Gen};
753    use quickcheck_macros::quickcheck;
754    use rstest::rstest;
755    use std::ops::Deref;
756    use std::panic::{catch_unwind, set_hook, take_hook, UnwindSafe};
757
758    fn catch_unwind_silent<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> std::thread::Result<R> {
759        let prev_hook = take_hook();
760        set_hook(Box::new(|_| {}));
761        let result = catch_unwind(f);
762        set_hook(prev_hook);
763        result
764    }
765
766    #[rstest]
767    #[case(0.0, 0, 0)]
768    #[case(0.0, 1, 0)]
769    #[case(1.0, 0, 1)]
770    #[case(1.0, -1, 0)]
771    #[case(12.3, -2, 0)]
772    #[case(12.3, -1, 3)]
773    #[case(12.3, 0, 2)]
774    #[case(12.3, 1, 1)]
775    #[case(12.3, 2, 0)]
776    #[case(-1.0, 0, 1)]
777    #[case(-1.0, 1, 0)]
778    fn get_digit(#[case] number: f64, #[case] index: isize, #[case] expected: u8) {
779        match Number::new(&StringNumber::from(number).0) {
780            Number::Positive(n) => {
781                assert_eq!(n.get_digit(index), expected)
782            }
783            Number::Negative(n) => {
784                assert_eq!(n.get_digit(index), expected)
785            }
786            Number::NaN => unreachable!(),
787        }
788    }
789
790    #[rstest]
791    #[case(0.0, 0.0, Some(Ordering::Equal))] // 1
792    #[case(0.0, -0.0, Some(Ordering::Equal))] // 2
793    #[case(1.0, 0.0, Some(Ordering::Greater))] // 3
794    #[case(0.0, 1.0, Some(Ordering::Less))] // 4
795    #[case(0.0, -1.0, Some(Ordering::Greater))] // 5
796    #[case(-1.0, 0.0, Some(Ordering::Less))] // 6
797    #[case(-1.0, 1.0, Some(Ordering::Less))] // 7
798    #[case(1.0, -1.0, Some(Ordering::Greater))] // 8
799    #[case(-1.0, -1.0, Some(Ordering::Equal))] // 9
800    #[case(-1.0, -2.0, Some(Ordering::Greater))] // 10
801    #[case(120.0, 21.0, Some(Ordering::Greater))] // 11
802    #[case(-120.0, -21.0, Some(Ordering::Less))] // 12
803    #[case(0.1, 0.2, Some(Ordering::Less))] // 13
804    #[case(0.2, 0.1, Some(Ordering::Greater))] // 14
805    #[case(f64::NAN, f64::NAN, None)] // 15
806    #[case(f64::INFINITY, 0.0, Some(Ordering::Greater))] // 16
807    #[case(1000.0, f64::INFINITY, Some(Ordering::Less))] // 17
808    #[case(f64::NEG_INFINITY, 0.0, Some(Ordering::Less))] // 18
809    #[case(f64::NEG_INFINITY, f64::INFINITY, Some(Ordering::Less))] // 19
810    fn partial_cmp(#[case] a: f64, #[case] b: f64, #[case] expected: Option<Ordering>) {
811        assert_eq!(StringNumber::from(a).partial_cmp(&b.into()), expected);
812    }
813
814    #[quickcheck]
815    fn partial_cmp_quickcheck(a: NoShrink<f64>, b: NoShrink<f64>) -> bool {
816        let a = a.into_inner();
817        let b = b.into_inner();
818        StringNumber::from(a).partial_cmp(&b.into()) == a.partial_cmp(&b)
819    }
820
821    #[rstest]
822    #[case(0.0, 0.0, 0.0)] // 1
823    #[case(1.0, 0.0, 1.0)] // 2
824    #[case(0.0, 1.0, 1.0)] // 3
825    #[case(1.0, 2.0, 3.0)] // 4
826    #[case(1.0, 10.0, 11.0)] // 5
827    #[case(5.0, 5.0, 10.0)] // 6
828    #[case(15.0, 5.0, 20.0)] // 7
829    #[case(15.0, 16.0, 31.0)] // 8
830    #[case(55.0, 65.0, 120.0)] // 9
831    #[case(0.0, -0.0, 0.0)] // 10
832    #[case(0.0, -1.0, -1.0)] // 11
833    #[case(1.0, -1.0, 0.0)] // 12
834    #[case(0.1, 0.2, 0.3)] // 13
835    #[case(0.1, -0.2, -0.1)] // 14
836    #[case(-0.1, 0.2, 0.1)] // 15
837    #[case(0.1, 0.02, 0.12)] // 16
838    #[case(0.09, 0.03, 0.12)] // 17
839    #[case(0.9, 0.3, 1.2)] // 18
840    #[case(f64::NAN, 0.0, f64::NAN)] // 19
841    #[case(f64::INFINITY, 1.0, f64::INFINITY)] // 20
842    #[case(f64::NEG_INFINITY, 1.0, f64::NEG_INFINITY)] // 21
843    #[case(f64::NEG_INFINITY, f64::INFINITY, f64::NAN)] // 22
844    #[case(f64::INFINITY, f64::NEG_INFINITY, f64::NAN)] // 23
845    #[case(0.0, 1.2, 1.2)] // 24
846    fn add(#[case] a: f64, #[case] b: f64, #[case] expected: f64) {
847        assert_eq!(
848            StringNumber::from(a) + StringNumber::from(b),
849            StringNumber::from(expected)
850        );
851    }
852
853    #[quickcheck]
854    fn add_quickcheck(a: NoShrink<BigDecimal>, b: NoShrink<BigDecimal>) -> bool {
855        let a = a.into_inner().into_inner();
856        let b = b.into_inner().into_inner();
857        StringNumber::from(&a) + StringNumber::from(&b) == StringNumber::from(&(a + b))
858    }
859
860    #[rstest]
861    #[case(0.0, 0.0, 0.0)] // 1
862    #[case(1.0, 0.0, 1.0)] // 2
863    #[case(0.0, 1.0, -1.0)] // 3
864    #[case(-1.0, 0.0, -1.0)] // 4
865    #[case(0.0, -1.0, 1.0)] // 5
866    #[case(1.0, 1.0, 0.0)] // 6
867    #[case(1.0, -1.0, 2.0)] // 7
868    #[case(-1.0, 1.0, -2.0)] // 8
869    #[case(f64::NAN, 0.0, f64::NAN)] // 9
870    #[case(f64::INFINITY, 1.0, f64::INFINITY)] // 10
871    #[case(f64::NEG_INFINITY, 1.0, f64::NEG_INFINITY)] // 11
872    #[case(f64::INFINITY, f64::INFINITY, f64::NAN)] // 12
873    fn sub(#[case] a: f64, #[case] b: f64, #[case] expected: f64) {
874        assert_eq!(
875            StringNumber::from(a) - StringNumber::from(b),
876            StringNumber::from(expected)
877        );
878    }
879
880    #[quickcheck]
881    fn sub_quickcheck(a: NoShrink<BigDecimal>, b: NoShrink<BigDecimal>) -> bool {
882        let a = a.into_inner().into_inner();
883        let b = b.into_inner().into_inner();
884        StringNumber::from(&a) - StringNumber::from(&b) == StringNumber::from(&(a - b))
885    }
886
887    #[rstest]
888    #[case(0.0, 0, -1)]
889    #[case(1.0, 0, -1)]
890    #[case(1.2, 0, -1)]
891    #[case(12.34, 1, -2)]
892    fn left_most_index_right_most_index(
893        #[case] f: f64,
894        #[case] expected_left_most_index: isize,
895        #[case] expected_right_most_index: isize,
896    ) {
897        let number = PositiveNumber::from(f);
898        assert_eq!(number.left_most_index(), expected_left_most_index);
899        assert_eq!(number.right_most_index(), expected_right_most_index);
900    }
901
902    #[rstest]
903    #[case(vec![], 0, "0.0")]
904    #[case(vec![0], 0, "0.0")]
905    #[case(vec![0], 1, "0.0")]
906    #[case(vec![1, 0], 0, "0.01")]
907    #[case(vec![1, 0], 1, "0.1")]
908    #[case(vec![1, 0], 2, "01.0")]
909    #[case(vec![0, 1], 0, "0.10")]
910    #[case(vec![0, 1], 1, "1.0")]
911    #[case(vec![0, 1], 2, "10.0")]
912    fn digits_to_string(
913        #[case] digits: Vec<u8>,
914        #[case] decimal_index: usize,
915        #[case] expected: &str,
916    ) {
917        assert_eq!(
918            PositiveNumber::digits_to_string(digits, decimal_index),
919            expected
920        );
921    }
922
923    #[test]
924    fn digits_to_string_panic() {
925        assert!(catch_unwind_silent(|| PositiveNumber::digits_to_string(vec![], 2)).is_err());
926    }
927
928    #[rstest]
929    #[case(0.0, 0, 0.0)] // 1
930    #[case(1.0, 0, 1.0)] // 2
931    #[case(0.0, 1, 0.0)] // 3
932    #[case(1.0, 1, 10.0)] // 4
933    #[case(1.0, -1, 0.1)] // 5
934    #[case(0.1, 1, 1.0)] // 6
935    #[case(0.1, -1, 0.01)] // 7
936    #[case(10.2, 2, 1020.0)] // 8
937    #[case(1.2, -1, 0.12)] // 9
938    #[case(1.2, -2, 0.012)] // 10
939    fn mul_10_power(#[case] number: f64, #[case] power: isize, #[case] expected: f64) {
940        assert_eq!(
941            PositiveNumber::from(number).mul_10_power(power),
942            PositiveNumber::from(expected)
943        );
944    }
945
946    #[rstest]
947    #[case(0.0, 0, 0.0)]
948    #[case(1.0, 0, 0.0)]
949    #[case(0.0, 1, 0.0)]
950    #[case(1.0, 1, 1.0)]
951    #[case(2.0, 1, 2.0)]
952    #[case(1.0, 2, 2.0)]
953    #[case(1.2, 2, 2.4)]
954    #[case(1.2, 2, 2.4)]
955    #[case(12.34, 2, 24.68)]
956    #[case(0.2, 8, 1.6)]
957    #[case(12.34, 4, 49.36)]
958    #[case(12.34, 9, 111.06)]
959    #[case(12.34, 0, 0.0)]
960    fn mul_by_single_digit(#[case] number: f64, #[case] n: u8, #[case] expected: f64) {
961        assert_eq!(
962            PositiveNumber::from(number).mul_by_single_digit(n),
963            PositiveNumber::from(expected)
964        );
965    }
966
967    #[rstest]
968    #[case(0.0, 0.0, 0.0)] // 1
969    #[case(0.0, 1.0, 0.0)] // 2
970    #[case(1.0, 0.0, 0.0)] // 3
971    #[case(1.0, 1.0, 1.0)] // 4
972    #[case(12.0, 1.0, 12.0)] // 5
973    #[case(1.0, 12.0, 12.0)] // 6
974    #[case(12.0, 34.0, 408.0)] // 7
975    #[case(7.9, 6.8, 53.72)] // 8
976    #[case(1.0, -1.0, -1.0)] // 9
977    #[case(-1.0, 1.0, -1.0)] // 10
978    #[case(-1.0, -1.0, 1.0)] // 11
979    #[case(f64::NAN, 0.0, f64::NAN)] // 12
980    #[case(0.0, f64::NAN, f64::NAN)] // 13
981    #[case(f64::INFINITY, 1.0, f64::INFINITY)] // 14
982    #[case(1.0, f64::INFINITY, f64::INFINITY)] // 15
983    #[case(f64::INFINITY, 0.0, f64::NAN)] // 16
984    #[case(0.0, f64::INFINITY, f64::NAN)] // 17
985    fn mul(#[case] a: f64, #[case] b: f64, #[case] expected: f64) {
986        assert_eq!(
987            StringNumber::from(a) * StringNumber::from(b),
988            StringNumber::from(expected)
989        )
990    }
991
992    #[quickcheck]
993    fn mul_quickcheck(a: NoShrink<BigDecimal>, b: NoShrink<BigDecimal>) -> bool {
994        let a = a.into_inner().into_inner();
995        let b = b.into_inner().into_inner();
996        StringNumber::from(&a) * StringNumber::from(&b) == StringNumber::from(&(a * b))
997    }
998
999    #[rstest]
1000    #[case("0", "0.0")]
1001    #[case("0.0", "0.0")]
1002    #[case("001.0", "1.0")]
1003    #[case("-001.0", "-1.0")]
1004    #[case("0.100", "0.1")]
1005    #[case(".1", "0.1")]
1006    #[case("-.1", "-0.1")]
1007    fn fix_zeros(#[case] s: &str, #[case] expected: &str) {
1008        let mut result = s.to_string();
1009        StringNumber::fix_zeros(&mut result);
1010        assert_eq!(result, expected);
1011    }
1012
1013    #[derive(Debug, Clone)]
1014    struct BigDecimal(bigdecimal::BigDecimal);
1015
1016    impl BigDecimal {
1017        fn into_inner(self) -> bigdecimal::BigDecimal {
1018            self.0
1019        }
1020    }
1021
1022    impl Arbitrary for BigDecimal {
1023        fn arbitrary(g: &mut Gen) -> Self {
1024            let sign = match u64::arbitrary(g) % 3 {
1025                0 => Sign::Minus,
1026                1 => Sign::NoSign,
1027                2 => Sign::Plus,
1028                _ => unreachable!(),
1029            };
1030            let mut digits: Vec<u32> = Vec::new();
1031            digits.resize_with(u8::arbitrary(g) as usize, || Arbitrary::arbitrary(g));
1032            Self(bigdecimal::BigDecimal::new(
1033                BigInt::new(sign, digits),
1034                u8::arbitrary(g) as i64,
1035            ))
1036        }
1037    }
1038
1039    impl Deref for BigDecimal {
1040        type Target = bigdecimal::BigDecimal;
1041
1042        fn deref(&self) -> &Self::Target {
1043            &self.0
1044        }
1045    }
1046
1047    // https://github.com/BurntSushi/quickcheck/pull/293/files
1048    #[derive(Clone, Debug)]
1049    struct NoShrink<A: Arbitrary> {
1050        inner: A,
1051    }
1052
1053    impl<A: Arbitrary> NoShrink<A> {
1054        fn into_inner(self) -> A {
1055            self.inner
1056        }
1057    }
1058
1059    impl<A: Arbitrary> Arbitrary for NoShrink<A> {
1060        fn arbitrary(gen: &mut Gen) -> Self {
1061            Self {
1062                inner: Arbitrary::arbitrary(gen),
1063            }
1064        }
1065    }
1066
1067    impl<A: Arbitrary> AsRef<A> for NoShrink<A> {
1068        fn as_ref(&self) -> &A {
1069            &self.inner
1070        }
1071    }
1072}