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#[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 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: 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 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.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 if decimal_index == bytes.len() - 1 {
434 bytes.push(b'0');
435 }
436
437 String::from_utf8(bytes).unwrap()
438 }
439
440 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 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: 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 fn get_digit(&self, mut index: isize) -> u8 {
732 if index < 0 {
733 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))] #[case(0.0, -0.0, Some(Ordering::Equal))] #[case(1.0, 0.0, Some(Ordering::Greater))] #[case(0.0, 1.0, Some(Ordering::Less))] #[case(0.0, -1.0, Some(Ordering::Greater))] #[case(-1.0, 0.0, Some(Ordering::Less))] #[case(-1.0, 1.0, Some(Ordering::Less))] #[case(1.0, -1.0, Some(Ordering::Greater))] #[case(-1.0, -1.0, Some(Ordering::Equal))] #[case(-1.0, -2.0, Some(Ordering::Greater))] #[case(120.0, 21.0, Some(Ordering::Greater))] #[case(-120.0, -21.0, Some(Ordering::Less))] #[case(0.1, 0.2, Some(Ordering::Less))] #[case(0.2, 0.1, Some(Ordering::Greater))] #[case(f64::NAN, f64::NAN, None)] #[case(f64::INFINITY, 0.0, Some(Ordering::Greater))] #[case(1000.0, f64::INFINITY, Some(Ordering::Less))] #[case(f64::NEG_INFINITY, 0.0, Some(Ordering::Less))] #[case(f64::NEG_INFINITY, f64::INFINITY, Some(Ordering::Less))] 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)] #[case(1.0, 0.0, 1.0)] #[case(0.0, 1.0, 1.0)] #[case(1.0, 2.0, 3.0)] #[case(1.0, 10.0, 11.0)] #[case(5.0, 5.0, 10.0)] #[case(15.0, 5.0, 20.0)] #[case(15.0, 16.0, 31.0)] #[case(55.0, 65.0, 120.0)] #[case(0.0, -0.0, 0.0)] #[case(0.0, -1.0, -1.0)] #[case(1.0, -1.0, 0.0)] #[case(0.1, 0.2, 0.3)] #[case(0.1, -0.2, -0.1)] #[case(-0.1, 0.2, 0.1)] #[case(0.1, 0.02, 0.12)] #[case(0.09, 0.03, 0.12)] #[case(0.9, 0.3, 1.2)] #[case(f64::NAN, 0.0, f64::NAN)] #[case(f64::INFINITY, 1.0, f64::INFINITY)] #[case(f64::NEG_INFINITY, 1.0, f64::NEG_INFINITY)] #[case(f64::NEG_INFINITY, f64::INFINITY, f64::NAN)] #[case(f64::INFINITY, f64::NEG_INFINITY, f64::NAN)] #[case(0.0, 1.2, 1.2)] 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)] #[case(1.0, 0.0, 1.0)] #[case(0.0, 1.0, -1.0)] #[case(-1.0, 0.0, -1.0)] #[case(0.0, -1.0, 1.0)] #[case(1.0, 1.0, 0.0)] #[case(1.0, -1.0, 2.0)] #[case(-1.0, 1.0, -2.0)] #[case(f64::NAN, 0.0, f64::NAN)] #[case(f64::INFINITY, 1.0, f64::INFINITY)] #[case(f64::NEG_INFINITY, 1.0, f64::NEG_INFINITY)] #[case(f64::INFINITY, f64::INFINITY, f64::NAN)] 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)] #[case(1.0, 0, 1.0)] #[case(0.0, 1, 0.0)] #[case(1.0, 1, 10.0)] #[case(1.0, -1, 0.1)] #[case(0.1, 1, 1.0)] #[case(0.1, -1, 0.01)] #[case(10.2, 2, 1020.0)] #[case(1.2, -1, 0.12)] #[case(1.2, -2, 0.012)] 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)] #[case(0.0, 1.0, 0.0)] #[case(1.0, 0.0, 0.0)] #[case(1.0, 1.0, 1.0)] #[case(12.0, 1.0, 12.0)] #[case(1.0, 12.0, 12.0)] #[case(12.0, 34.0, 408.0)] #[case(7.9, 6.8, 53.72)] #[case(1.0, -1.0, -1.0)] #[case(-1.0, 1.0, -1.0)] #[case(-1.0, -1.0, 1.0)] #[case(f64::NAN, 0.0, f64::NAN)] #[case(0.0, f64::NAN, f64::NAN)] #[case(f64::INFINITY, 1.0, f64::INFINITY)] #[case(1.0, f64::INFINITY, f64::INFINITY)] #[case(f64::INFINITY, 0.0, f64::NAN)] #[case(0.0, f64::INFINITY, f64::NAN)] 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 #[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}