Skip to main content

smart_big_rational/
lib.rs

1// Copyright 2023-2026 The SmartBigRational Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! This crate implements a big rational type that is optimized for addition,
16//! subtraction and multiplication.
17//!
18//! Unlike the vanilla [`BigRational`] type, the [`SmartBigRational`] doesn't
19//! perform a full GCD reduction upon addition and subtraction, and doesn't
20//! perform any reduction upon multiplication.
21
22#![forbid(missing_docs, unsafe_code)]
23
24mod denom;
25
26pub use denom::Denom;
27use num_bigint::{BigInt, BigUint, Sign};
28use num_rational::BigRational;
29use num_traits::{One, Pow, Zero};
30use std::cmp::Ordering;
31use std::fmt::Display;
32use std::iter::{Product, Sum};
33use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
34
35/// A big rational type optimized for addition, subtraction and multiplication.
36///
37/// This is achieved by representing the denominator with the [`Denom`] type,
38/// and performing only partial GCD reductions during arithmetic operations.
39#[derive(Clone, Debug)]
40pub struct SmartBigRational {
41    num: BigInt,
42    denom: Denom,
43}
44
45impl SmartBigRational {
46    /// Constant value of 0.
47    pub const ZERO: Self = Self {
48        num: BigInt::ZERO,
49        denom: Denom::ONE,
50    };
51
52    /// Constant value of 1.
53    pub const ONE: Self = Self {
54        num: BigInt::ONE,
55        denom: Denom::ONE,
56    };
57
58    /// Creates a new rational number by dividing the given numerator by the
59    /// denominator.
60    ///
61    /// ```
62    /// # use num_bigint::BigInt;
63    /// # use num_rational::BigRational;
64    /// # use smart_big_rational::SmartBigRational;
65    /// let x = SmartBigRational::ratio(2, 3u32);
66    ///
67    /// assert_eq!(
68    ///     BigRational::from(x),
69    ///     BigRational::new(BigInt::from(2), BigInt::from(3))
70    /// );
71    /// ```
72    pub fn ratio(num: impl Into<BigInt>, denom: impl Into<Denom>) -> Self {
73        Self {
74            num: num.into(),
75            denom: denom.into(),
76        }
77    }
78
79    /// Converts this value to a [`BigRational`].
80    pub fn into_big_rational(self) -> BigRational {
81        self.into()
82    }
83
84    /// Converts this value to a [`BigRational`].
85    pub fn to_big_rational(&self) -> BigRational {
86        self.into()
87    }
88
89    /// Reduces the current value.
90    ///
91    /// After reduction, the GCD of the numerator and denominator is one.
92    ///
93    /// This is a slow operation, but may be beneficial in some cases (for
94    /// example if this value is then used many times) as the representation
95    /// becomes smaller if the numerator and denominator had many common
96    /// factors. This may however be detrimental if you then add/subtract values
97    /// that contain the same common factors that were reduced. Therefore,
98    /// there is no rule of thumb: benchmark your concrete code to see if
99    /// this brings any performance improvement.
100    pub fn reduce(&mut self) {
101        self.denom.gcd_reduce(&mut self.num);
102    }
103}
104
105impl From<BigRational> for SmartBigRational {
106    fn from(value: BigRational) -> SmartBigRational {
107        let (num, denom) = value.into_raw();
108        let (sign, denom) = denom.into_parts();
109        assert_eq!(sign, Sign::Plus);
110        SmartBigRational {
111            num,
112            denom: denom.into(),
113        }
114    }
115}
116
117impl From<&BigRational> for SmartBigRational {
118    fn from(value: &BigRational) -> SmartBigRational {
119        let denom = value.denom();
120        assert_eq!(denom.sign(), Sign::Plus);
121        SmartBigRational {
122            num: value.numer().clone(),
123            denom: denom.magnitude().into(),
124        }
125    }
126}
127
128impl From<BigInt> for SmartBigRational {
129    fn from(value: BigInt) -> SmartBigRational {
130        SmartBigRational {
131            num: value,
132            denom: Denom::ONE,
133        }
134    }
135}
136
137impl From<SmartBigRational> for BigRational {
138    fn from(value: SmartBigRational) -> BigRational {
139        BigRational::new(value.num, value.denom.to_biguint().into())
140    }
141}
142
143impl From<&SmartBigRational> for BigRational {
144    fn from(value: &SmartBigRational) -> BigRational {
145        BigRational::new(value.num.clone(), value.denom.to_biguint().into())
146    }
147}
148
149impl PartialEq for SmartBigRational {
150    fn eq(&self, rhs: &Self) -> bool {
151        self.num.sign() == rhs.num.sign()
152            && self.num.magnitude() * rhs.denom.to_biguint()
153                == rhs.num.magnitude() * self.denom.to_biguint()
154    }
155}
156
157impl Eq for SmartBigRational {}
158
159impl PartialOrd for SmartBigRational {
160    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
161        Some(self.cmp(rhs))
162    }
163}
164
165impl Ord for SmartBigRational {
166    fn cmp(&self, rhs: &Self) -> Ordering {
167        match (self.num.sign(), rhs.num.sign()) {
168            (Sign::Plus, Sign::Plus) => (self.num.magnitude() * rhs.denom.to_biguint())
169                .cmp(&(rhs.num.magnitude() * self.denom.to_biguint())),
170            (Sign::Plus, Sign::NoSign) => Ordering::Greater,
171            (Sign::Plus, Sign::Minus) => Ordering::Greater,
172            (Sign::NoSign, Sign::Plus) => Ordering::Less,
173            (Sign::NoSign, Sign::NoSign) => Ordering::Equal,
174            (Sign::NoSign, Sign::Minus) => Ordering::Greater,
175            (Sign::Minus, Sign::Plus) => Ordering::Less,
176            (Sign::Minus, Sign::NoSign) => Ordering::Less,
177            (Sign::Minus, Sign::Minus) => (rhs.num.magnitude() * self.denom.to_biguint())
178                .cmp(&(self.num.magnitude() * rhs.denom.to_biguint())),
179        }
180    }
181}
182
183impl Display for SmartBigRational {
184    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
185        Display::fmt(&self.to_big_rational(), f)
186    }
187}
188
189impl Zero for SmartBigRational {
190    fn zero() -> Self {
191        Self::ZERO
192    }
193
194    fn is_zero(&self) -> bool {
195        self.num.is_zero()
196    }
197}
198
199impl One for SmartBigRational {
200    fn one() -> Self {
201        Self::ONE
202    }
203}
204
205impl Neg for SmartBigRational {
206    type Output = Self;
207
208    fn neg(self) -> Self {
209        SmartBigRational {
210            num: -self.num,
211            denom: self.denom,
212        }
213    }
214}
215
216impl Neg for &SmartBigRational {
217    type Output = SmartBigRational;
218
219    fn neg(self) -> SmartBigRational {
220        SmartBigRational {
221            num: -&self.num,
222            denom: self.denom.clone(),
223        }
224    }
225}
226
227impl Pow<u32> for SmartBigRational {
228    type Output = Self;
229
230    fn pow(self, rhs: u32) -> Self {
231        SmartBigRational {
232            num: self.num.pow(rhs),
233            denom: self.denom.pow(rhs),
234        }
235    }
236}
237
238impl Pow<u32> for &SmartBigRational {
239    type Output = SmartBigRational;
240
241    fn pow(self, rhs: u32) -> SmartBigRational {
242        SmartBigRational {
243            num: Pow::pow(&self.num, rhs),
244            denom: Pow::pow(&self.denom, rhs),
245        }
246    }
247}
248
249impl Add for SmartBigRational {
250    type Output = Self;
251
252    fn add(mut self, mut rhs: Self) -> Self {
253        let denom = Denom::normalize(&mut self.num, &mut rhs.num, &self.denom, &rhs.denom);
254        SmartBigRational {
255            num: self.num + rhs.num,
256            denom,
257        }
258    }
259}
260
261impl Add<&SmartBigRational> for SmartBigRational {
262    type Output = Self;
263
264    fn add(mut self, rhs: &SmartBigRational) -> SmartBigRational {
265        let mut rhs_num = rhs.num.clone();
266        let denom = Denom::normalize(&mut self.num, &mut rhs_num, &self.denom, &rhs.denom);
267        SmartBigRational {
268            num: self.num + rhs_num,
269            denom,
270        }
271    }
272}
273
274impl Add for &SmartBigRational {
275    type Output = SmartBigRational;
276
277    fn add(self, rhs: Self) -> SmartBigRational {
278        let mut num = self.num.clone();
279        let mut rhs_num = rhs.num.clone();
280        let denom = Denom::normalize(&mut num, &mut rhs_num, &self.denom, &rhs.denom);
281        SmartBigRational {
282            num: num + rhs_num,
283            denom,
284        }
285    }
286}
287
288impl Add<SmartBigRational> for &SmartBigRational {
289    type Output = SmartBigRational;
290
291    fn add(self, mut rhs: SmartBigRational) -> SmartBigRational {
292        let mut num = self.num.clone();
293        let denom = Denom::normalize(&mut num, &mut rhs.num, &self.denom, &rhs.denom);
294        SmartBigRational {
295            num: num + rhs.num,
296            denom,
297        }
298    }
299}
300
301impl AddAssign for SmartBigRational {
302    fn add_assign(&mut self, mut rhs: Self) {
303        self.denom = Denom::normalize(&mut self.num, &mut rhs.num, &self.denom, &rhs.denom);
304        self.num += rhs.num;
305    }
306}
307
308impl AddAssign<&SmartBigRational> for SmartBigRational {
309    fn add_assign(&mut self, rhs: &SmartBigRational) {
310        let mut rhs_num = rhs.num.clone();
311        self.denom = Denom::normalize(&mut self.num, &mut rhs_num, &self.denom, &rhs.denom);
312        self.num += rhs_num;
313    }
314}
315
316impl Add<BigInt> for SmartBigRational {
317    type Output = Self;
318
319    fn add(self, mut rhs: BigInt) -> Self {
320        rhs *= &self.denom;
321        SmartBigRational {
322            num: self.num + rhs,
323            denom: self.denom,
324        }
325    }
326}
327
328impl Add<&BigInt> for SmartBigRational {
329    type Output = Self;
330
331    fn add(self, rhs: &BigInt) -> Self {
332        SmartBigRational {
333            num: self.num + rhs * &self.denom,
334            denom: self.denom,
335        }
336    }
337}
338
339impl Add<BigInt> for &SmartBigRational {
340    type Output = SmartBigRational;
341
342    fn add(self, mut rhs: BigInt) -> SmartBigRational {
343        rhs *= &self.denom;
344        SmartBigRational {
345            num: &self.num + rhs,
346            denom: self.denom.clone(),
347        }
348    }
349}
350
351impl Add<&BigInt> for &SmartBigRational {
352    type Output = SmartBigRational;
353
354    fn add(self, rhs: &BigInt) -> SmartBigRational {
355        SmartBigRational {
356            num: &self.num + rhs * &self.denom,
357            denom: self.denom.clone(),
358        }
359    }
360}
361
362impl AddAssign<BigInt> for SmartBigRational {
363    fn add_assign(&mut self, mut rhs: BigInt) {
364        rhs *= &self.denom;
365        self.num += rhs;
366    }
367}
368
369impl AddAssign<&BigInt> for SmartBigRational {
370    fn add_assign(&mut self, rhs: &BigInt) {
371        self.num += rhs * &self.denom;
372    }
373}
374
375impl Sub for SmartBigRational {
376    type Output = Self;
377
378    fn sub(mut self, mut rhs: Self) -> Self {
379        let denom = Denom::normalize(&mut self.num, &mut rhs.num, &self.denom, &rhs.denom);
380        SmartBigRational {
381            num: self.num - rhs.num,
382            denom,
383        }
384    }
385}
386
387impl Sub<&SmartBigRational> for SmartBigRational {
388    type Output = Self;
389
390    fn sub(mut self, rhs: &SmartBigRational) -> Self {
391        let mut rhs_num = rhs.num.clone();
392        let denom = Denom::normalize(&mut self.num, &mut rhs_num, &self.denom, &rhs.denom);
393        SmartBigRational {
394            num: self.num - rhs_num,
395            denom,
396        }
397    }
398}
399
400impl Sub for &SmartBigRational {
401    type Output = SmartBigRational;
402
403    fn sub(self, rhs: Self) -> SmartBigRational {
404        let mut num = self.num.clone();
405        let mut rhs_num = rhs.num.clone();
406        let denom = Denom::normalize(&mut num, &mut rhs_num, &self.denom, &rhs.denom);
407        SmartBigRational {
408            num: num - rhs_num,
409            denom,
410        }
411    }
412}
413
414impl Sub<SmartBigRational> for &SmartBigRational {
415    type Output = SmartBigRational;
416
417    fn sub(self, mut rhs: SmartBigRational) -> SmartBigRational {
418        let mut num = self.num.clone();
419        let denom = Denom::normalize(&mut num, &mut rhs.num, &self.denom, &rhs.denom);
420        SmartBigRational {
421            num: num - rhs.num,
422            denom,
423        }
424    }
425}
426
427impl SubAssign for SmartBigRational {
428    fn sub_assign(&mut self, mut rhs: Self) {
429        self.denom = Denom::normalize(&mut self.num, &mut rhs.num, &self.denom, &rhs.denom);
430        self.num -= rhs.num;
431    }
432}
433
434impl SubAssign<&SmartBigRational> for SmartBigRational {
435    fn sub_assign(&mut self, rhs: &SmartBigRational) {
436        let mut rhs_num = rhs.num.clone();
437        self.denom = Denom::normalize(&mut self.num, &mut rhs_num, &self.denom, &rhs.denom);
438        self.num -= rhs_num;
439    }
440}
441
442impl Sub<BigInt> for SmartBigRational {
443    type Output = Self;
444
445    fn sub(self, mut rhs: BigInt) -> Self {
446        rhs *= &self.denom;
447        SmartBigRational {
448            num: self.num - rhs,
449            denom: self.denom,
450        }
451    }
452}
453
454impl Sub<&BigInt> for SmartBigRational {
455    type Output = Self;
456
457    fn sub(self, rhs: &BigInt) -> Self {
458        SmartBigRational {
459            num: self.num - rhs * &self.denom,
460            denom: self.denom,
461        }
462    }
463}
464
465impl Sub<BigInt> for &SmartBigRational {
466    type Output = SmartBigRational;
467
468    fn sub(self, mut rhs: BigInt) -> SmartBigRational {
469        rhs *= &self.denom;
470        SmartBigRational {
471            num: &self.num - rhs,
472            denom: self.denom.clone(),
473        }
474    }
475}
476
477impl Sub<&BigInt> for &SmartBigRational {
478    type Output = SmartBigRational;
479
480    fn sub(self, rhs: &BigInt) -> SmartBigRational {
481        SmartBigRational {
482            num: &self.num - rhs * &self.denom,
483            denom: self.denom.clone(),
484        }
485    }
486}
487
488impl SubAssign<BigInt> for SmartBigRational {
489    fn sub_assign(&mut self, mut rhs: BigInt) {
490        rhs *= &self.denom;
491        self.num -= rhs;
492    }
493}
494
495impl SubAssign<&BigInt> for SmartBigRational {
496    fn sub_assign(&mut self, rhs: &BigInt) {
497        self.num -= rhs * &self.denom;
498    }
499}
500
501impl Mul for SmartBigRational {
502    type Output = Self;
503
504    fn mul(self, rhs: Self) -> Self {
505        SmartBigRational {
506            num: self.num * rhs.num,
507            denom: self.denom * rhs.denom,
508        }
509    }
510}
511
512impl Mul<&SmartBigRational> for SmartBigRational {
513    type Output = Self;
514
515    fn mul(self, rhs: &SmartBigRational) -> Self {
516        SmartBigRational {
517            num: self.num * &rhs.num,
518            denom: self.denom * &rhs.denom,
519        }
520    }
521}
522
523impl Mul for &SmartBigRational {
524    type Output = SmartBigRational;
525
526    fn mul(self, rhs: Self) -> SmartBigRational {
527        SmartBigRational {
528            num: &self.num * &rhs.num,
529            denom: &self.denom * &rhs.denom,
530        }
531    }
532}
533
534impl Mul<SmartBigRational> for &SmartBigRational {
535    type Output = SmartBigRational;
536
537    fn mul(self, rhs: SmartBigRational) -> SmartBigRational {
538        SmartBigRational {
539            num: &self.num * rhs.num,
540            denom: &self.denom * rhs.denom,
541        }
542    }
543}
544
545impl MulAssign for SmartBigRational {
546    fn mul_assign(&mut self, rhs: Self) {
547        self.num *= rhs.num;
548        self.denom *= rhs.denom;
549    }
550}
551
552impl MulAssign<&SmartBigRational> for SmartBigRational {
553    fn mul_assign(&mut self, rhs: &SmartBigRational) {
554        self.num *= &rhs.num;
555        self.denom *= &rhs.denom;
556    }
557}
558
559impl Mul<BigInt> for SmartBigRational {
560    type Output = Self;
561
562    fn mul(self, rhs: BigInt) -> Self {
563        SmartBigRational {
564            num: self.num * rhs,
565            denom: self.denom,
566        }
567    }
568}
569
570impl Mul<&BigInt> for SmartBigRational {
571    type Output = Self;
572
573    fn mul(self, rhs: &BigInt) -> Self {
574        SmartBigRational {
575            num: self.num * rhs,
576            denom: self.denom,
577        }
578    }
579}
580
581impl Mul<BigInt> for &SmartBigRational {
582    type Output = SmartBigRational;
583
584    fn mul(self, rhs: BigInt) -> SmartBigRational {
585        SmartBigRational {
586            num: &self.num * rhs,
587            denom: self.denom.clone(),
588        }
589    }
590}
591
592impl Mul<&BigInt> for &SmartBigRational {
593    type Output = SmartBigRational;
594
595    fn mul(self, rhs: &BigInt) -> SmartBigRational {
596        SmartBigRational {
597            num: &self.num * rhs,
598            denom: self.denom.clone(),
599        }
600    }
601}
602
603impl MulAssign<BigInt> for SmartBigRational {
604    fn mul_assign(&mut self, rhs: BigInt) {
605        self.num *= rhs;
606    }
607}
608
609impl MulAssign<&BigInt> for SmartBigRational {
610    fn mul_assign(&mut self, rhs: &BigInt) {
611        self.num *= rhs;
612    }
613}
614
615impl Div for SmartBigRational {
616    type Output = Self;
617
618    fn div(self, rhs: Self) -> Self {
619        let (rhs_sign, rhs_num) = rhs.num.into_parts();
620        let rhs_denom = BigInt::from_biguint(rhs_sign, BigUint::from(rhs.denom));
621        SmartBigRational {
622            num: self.num * rhs_denom,
623            denom: self.denom * Denom::from(rhs_num),
624        }
625    }
626}
627
628impl Div<&SmartBigRational> for SmartBigRational {
629    type Output = Self;
630
631    fn div(self, rhs: &SmartBigRational) -> Self {
632        let rhs_denom = BigInt::from_biguint(rhs.num.sign(), BigUint::from(&rhs.denom));
633        SmartBigRational {
634            num: self.num * rhs_denom,
635            denom: self.denom * Denom::from(rhs.num.magnitude()),
636        }
637    }
638}
639
640impl Div for &SmartBigRational {
641    type Output = SmartBigRational;
642
643    fn div(self, rhs: Self) -> SmartBigRational {
644        let rhs_denom = BigInt::from_biguint(rhs.num.sign(), BigUint::from(&rhs.denom));
645        SmartBigRational {
646            num: &self.num * rhs_denom,
647            denom: &self.denom * Denom::from(rhs.num.magnitude()),
648        }
649    }
650}
651
652impl Div<SmartBigRational> for &SmartBigRational {
653    type Output = SmartBigRational;
654
655    fn div(self, rhs: SmartBigRational) -> SmartBigRational {
656        let (rhs_sign, rhs_num) = rhs.num.into_parts();
657        let rhs_denom = BigInt::from_biguint(rhs_sign, BigUint::from(rhs.denom));
658        SmartBigRational {
659            num: &self.num * rhs_denom,
660            denom: &self.denom * Denom::from(rhs_num),
661        }
662    }
663}
664
665impl DivAssign for SmartBigRational {
666    fn div_assign(&mut self, rhs: Self) {
667        let (rhs_sign, rhs_num) = rhs.num.into_parts();
668        let rhs_denom = BigInt::from_biguint(rhs_sign, BigUint::from(rhs.denom));
669        self.num *= rhs_denom;
670        self.denom *= Denom::from(rhs_num);
671    }
672}
673
674impl DivAssign<&SmartBigRational> for SmartBigRational {
675    fn div_assign(&mut self, rhs: &SmartBigRational) {
676        let rhs_denom = BigInt::from_biguint(rhs.num.sign(), BigUint::from(&rhs.denom));
677        self.num *= rhs_denom;
678        self.denom *= Denom::from(rhs.num.magnitude());
679    }
680}
681
682impl Div<BigUint> for SmartBigRational {
683    type Output = Self;
684
685    #[expect(clippy::suspicious_arithmetic_impl)]
686    fn div(self, rhs: BigUint) -> Self {
687        SmartBigRational {
688            num: self.num,
689            denom: self.denom * Denom::from(rhs),
690        }
691    }
692}
693
694impl Div<&BigUint> for SmartBigRational {
695    type Output = Self;
696
697    #[expect(clippy::suspicious_arithmetic_impl)]
698    fn div(self, rhs: &BigUint) -> Self {
699        SmartBigRational {
700            num: self.num,
701            denom: self.denom * Denom::from(rhs),
702        }
703    }
704}
705
706impl Div<BigUint> for &SmartBigRational {
707    type Output = SmartBigRational;
708
709    #[expect(clippy::suspicious_arithmetic_impl)]
710    fn div(self, rhs: BigUint) -> SmartBigRational {
711        SmartBigRational {
712            num: self.num.clone(),
713            denom: &self.denom * Denom::from(rhs),
714        }
715    }
716}
717
718impl Div<&BigUint> for &SmartBigRational {
719    type Output = SmartBigRational;
720
721    #[expect(clippy::suspicious_arithmetic_impl)]
722    fn div(self, rhs: &BigUint) -> SmartBigRational {
723        SmartBigRational {
724            num: self.num.clone(),
725            denom: &self.denom * Denom::from(rhs),
726        }
727    }
728}
729
730impl DivAssign<BigUint> for SmartBigRational {
731    #[expect(clippy::suspicious_op_assign_impl)]
732    fn div_assign(&mut self, rhs: BigUint) {
733        self.denom *= Denom::from(rhs);
734    }
735}
736
737impl DivAssign<&BigUint> for SmartBigRational {
738    #[expect(clippy::suspicious_op_assign_impl)]
739    fn div_assign(&mut self, rhs: &BigUint) {
740        self.denom *= Denom::from(rhs);
741    }
742}
743
744impl Sum for SmartBigRational {
745    fn sum<I>(iter: I) -> Self
746    where
747        I: Iterator<Item = Self>,
748    {
749        iter.fold(Self::zero(), |acc, x| acc + x)
750    }
751}
752
753impl<'a> Sum<&'a SmartBigRational> for SmartBigRational {
754    fn sum<I>(iter: I) -> Self
755    where
756        I: Iterator<Item = &'a SmartBigRational>,
757    {
758        iter.fold(Self::zero(), |acc, x| acc + x)
759    }
760}
761
762impl Product for SmartBigRational {
763    fn product<I>(iter: I) -> Self
764    where
765        I: Iterator<Item = Self>,
766    {
767        iter.fold(Self::one(), |acc, x| acc * x)
768    }
769}
770
771impl<'a> Product<&'a SmartBigRational> for SmartBigRational {
772    fn product<I>(iter: I) -> Self
773    where
774        I: Iterator<Item = &'a SmartBigRational>,
775    {
776        iter.fold(Self::one(), |acc, x| acc * x)
777    }
778}
779
780#[cfg(test)]
781mod tests {
782    use super::*;
783    use rand::seq::IndexedRandom;
784
785    fn get_positive_test_values() -> Vec<SmartBigRational> {
786        let mut result = Vec::new();
787        for i in 0..=30 {
788            result.push(SmartBigRational::ratio(1 << i, 1u32));
789        }
790        for i in 0..=30 {
791            result.push(SmartBigRational::ratio(1, 1u32 << i));
792        }
793        for i in 0..=30 {
794            result.push(SmartBigRational::ratio(0x7FFF_FFFF - (1 << i), 1u32));
795        }
796        for i in 0..=30 {
797            result.push(SmartBigRational::ratio(1, 0x7FFF_FFFF - (1u32 << i)));
798        }
799        result
800    }
801
802    fn loop_check1<T>(test_values: &[T], f: impl Fn(&T)) {
803        for a in test_values {
804            f(a);
805        }
806    }
807
808    fn loop_check2<T>(test_values: &[T], f: impl Fn(&T, &T)) {
809        for a in test_values {
810            for b in test_values {
811                f(a, b);
812            }
813        }
814    }
815
816    fn loop_check3<T>(test_values: &[T], num_samples: Option<usize>, f: impl Fn(&T, &T, &T)) {
817        match num_samples {
818            None => {
819                // Exhaustive check.
820                for a in test_values {
821                    for b in test_values {
822                        for c in test_values {
823                            f(a, b, c);
824                        }
825                    }
826                }
827            }
828            Some(n) => {
829                // Randomly sample values rather than conducting an exhaustive O(n^3) search on
830                // the test values.
831                let mut rng = rand::rng();
832
833                for _ in 0..n {
834                    let a = test_values.choose(&mut rng).unwrap();
835                    let b = test_values.choose(&mut rng).unwrap();
836                    let c = test_values.choose(&mut rng).unwrap();
837                    f(a, b, c);
838                }
839            }
840        }
841    }
842
843    #[test]
844    fn test_is_zero() {
845        let test_values = get_positive_test_values();
846        assert!(SmartBigRational::ZERO.is_zero());
847        assert!(!SmartBigRational::ONE.is_zero());
848        loop_check1(&test_values, |a| {
849            assert!(!a.is_zero(), "{a} is zero");
850        });
851    }
852
853    #[test]
854    fn test_zero_is_add_neutral() {
855        let test_values = get_positive_test_values();
856        loop_check1(&test_values, |a| {
857            assert_eq!(&(a + SmartBigRational::ZERO), a, "a + 0 != a for {a}");
858            assert_eq!(&(SmartBigRational::ZERO + a), a, "0 + a != a for {a}");
859            assert_eq!(&(a - SmartBigRational::ZERO), a, "a - 0 != a for {a}");
860        })
861    }
862
863    #[test]
864    fn test_add_is_commutative() {
865        let test_values = get_positive_test_values();
866        loop_check2(&test_values, |a, b| {
867            assert_eq!(a + b, b + a, "a + b != b + a for {a}, {b}");
868        })
869    }
870
871    #[test]
872    fn test_add_is_associative() {
873        let test_values = get_positive_test_values();
874        loop_check3(&test_values, None, |a, b, c| {
875            assert_eq!(
876                (a + b) + c,
877                a + (b + c),
878                "(a + b) + c != a + (b + c) for {a}, {b}, {c}"
879            );
880        })
881    }
882
883    #[test]
884    fn test_opposite() {
885        let test_values = get_positive_test_values();
886        loop_check1(&test_values, |a| {
887            assert_eq!(&-(-a), a, "-(-a) != a for {a}");
888            assert_eq!(
889                &(SmartBigRational::ZERO - (SmartBigRational::ZERO - a)),
890                a,
891                "0 - (0 - a) != a for {a}"
892            );
893        });
894    }
895
896    #[test]
897    fn test_sub_self() {
898        let test_values = get_positive_test_values();
899        loop_check1(&test_values, |a| {
900            assert_eq!(a - a, SmartBigRational::ZERO, "a - a != 0 for {a}");
901        });
902    }
903
904    #[test]
905    fn test_add_sub() {
906        let test_values = get_positive_test_values();
907        loop_check2(&test_values, |a, b| {
908            assert_eq!(&((a + b) - b), a, "(a + b) - b != a for {a}, {b}");
909        });
910    }
911
912    #[test]
913    fn test_sub_add() {
914        let test_values = get_positive_test_values();
915        loop_check2(&test_values, |a, b| {
916            assert_eq!(&((a - b) + b), a, "(a - b) + b != a for {a}, {b}");
917        });
918    }
919
920    #[test]
921    fn test_one_is_mul_neutral() {
922        let test_values = get_positive_test_values();
923        loop_check1(&test_values, |a| {
924            assert_eq!(&(a * SmartBigRational::ONE), a, "a * 1 != a for {a}");
925            assert_eq!(&(SmartBigRational::ONE * a), a, "1 * a != a for {a}");
926        })
927    }
928
929    #[test]
930    fn test_mul_is_commutative() {
931        let test_values = get_positive_test_values();
932        loop_check2(&test_values, |a, b| {
933            assert_eq!(a * b, b * a, "a * b != b * a for {a}, {b}");
934        })
935    }
936
937    #[test]
938    fn test_mul_is_associative() {
939        let test_values = get_positive_test_values();
940        loop_check3(&test_values, None, |a, b, c| {
941            assert_eq!(
942                (a * b) * c,
943                a * (b * c),
944                "(a * b) * c != a * (b * c) for {a}, {b}, {c}"
945            );
946        })
947    }
948
949    #[test]
950    fn test_mul_is_distributive() {
951        let test_values = get_positive_test_values();
952        loop_check3(&test_values, None, |a, b, c| {
953            assert_eq!(
954                a * (b + c),
955                (a * b) + (a * c),
956                "a * (b + c) != (a * b) + (a * c) for {a}, {b}, {c}"
957            );
958        })
959    }
960
961    #[test]
962    fn test_one_is_div_neutral() {
963        let test_values = get_positive_test_values();
964        loop_check1(&test_values, |a| {
965            assert_eq!(&(a / SmartBigRational::ONE), a, "a / 1 != a for {a}");
966        })
967    }
968
969    #[test]
970    fn test_div_self() {
971        let test_values = get_positive_test_values();
972        loop_check1(&test_values, |a| {
973            assert_eq!(a / a, SmartBigRational::ONE, "a / a != 1 for {a}");
974        });
975    }
976
977    #[test]
978    fn test_mul_div() {
979        let test_values = get_positive_test_values();
980        loop_check2(&test_values, |a, b| {
981            assert_eq!(&((a * b) / b), a, "(a * b) / b != a for {a}, {b}");
982        });
983    }
984}