Skip to main content

dashu_ratio/
rbig.rs

1use dashu_base::{EstimatedLog2, Sign};
2use dashu_int::{DoubleWord, IBig, UBig};
3
4use crate::{error::panic_divide_by_0, repr::Repr};
5
6/// An arbitrary precision rational number.
7///
8/// This struct represents a rational number with arbitrarily large numerator and denominator
9/// based on [UBig] and [IBig]. See the
10/// [user guide](https://zyxin.xyz/dashu/types.html) for construction,
11/// parsing, and the [`Relaxed`] variant.
12#[derive(PartialOrd, Ord)]
13#[cfg_attr(
14    feature = "rkyv_v07",
15    derive(rkyv_v07::Archive, rkyv_v07::Serialize, rkyv_v07::Deserialize)
16)]
17// `rkyv_v07` and `rkyv_v08` are mutually exclusive; when both are enabled (e.g. `--all-features`)
18// the 0.7 derive wins so the two versions' generated `Archived*`/`*Resolver` type names don't collide.
19#[cfg_attr(
20    all(feature = "rkyv_v08", not(feature = "rkyv_v07")),
21    derive(rkyv_v08::Archive, rkyv_v08::Serialize, rkyv_v08::Deserialize)
22)]
23#[cfg_attr(all(feature = "rkyv_v08", not(feature = "rkyv_v07")), rkyv(crate = rkyv_v08))]
24#[repr(transparent)]
25pub struct RBig(pub(crate) Repr);
26
27/// An arbitrary precision rational number without strict reduction.
28///
29/// This struct is almost the same as [RBig], except for that the numerator and the
30/// denominator are allowed to have common divisors **other than a power of 2**. This allows
31/// faster computation because [Gcd][dashu_base::Gcd] is not required for each operation.
32///
33/// Since the representation is not canonicalized, [Hash] is not implemented for [Relaxed].
34/// Please use [RBig] if you want to store the rational number in a hash set, or use `num_order::NumHash`.
35///
36/// # Conversion from/to [RBig]
37///
38/// To convert from [RBig], use [RBig::relax()]. To convert to [RBig], use [Relaxed::canonicalize()].
39#[derive(PartialEq, Eq, PartialOrd, Ord)]
40#[cfg_attr(
41    feature = "rkyv_v07",
42    derive(rkyv_v07::Archive, rkyv_v07::Serialize, rkyv_v07::Deserialize)
43)]
44// `rkyv_v07` and `rkyv_v08` are mutually exclusive; when both are enabled (e.g. `--all-features`)
45// the 0.7 derive wins so the two versions' generated `Archived*`/`*Resolver` type names don't collide.
46#[cfg_attr(
47    all(feature = "rkyv_v08", not(feature = "rkyv_v07")),
48    derive(rkyv_v08::Archive, rkyv_v08::Serialize, rkyv_v08::Deserialize)
49)]
50#[cfg_attr(all(feature = "rkyv_v08", not(feature = "rkyv_v07")), rkyv(crate = rkyv_v08))]
51#[repr(transparent)]
52pub struct Relaxed(pub(crate) Repr); // the result is not always normalized
53
54impl RBig {
55    /// [RBig] with value 0
56    pub const ZERO: Self = Self(Repr::zero());
57    /// [RBig] with value 1
58    pub const ONE: Self = Self(Repr::one());
59    /// [RBig] with value -1
60    pub const NEG_ONE: Self = Self(Repr::neg_one());
61
62    /// Create a rational number from a signed numerator and an unsigned denominator
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// # use dashu_int::{IBig, UBig};
68    /// # use dashu_ratio::RBig;
69    /// assert_eq!(RBig::from_parts(IBig::ZERO, UBig::ONE), RBig::ZERO);
70    /// assert_eq!(RBig::from_parts(IBig::ONE, UBig::ONE), RBig::ONE);
71    /// assert_eq!(RBig::from_parts(IBig::NEG_ONE, UBig::ONE), RBig::NEG_ONE);
72    /// ```
73    #[inline]
74    pub fn from_parts(numerator: IBig, denominator: UBig) -> Self {
75        if denominator.is_zero() {
76            panic_divide_by_0()
77        }
78
79        Self(
80            Repr {
81                numerator,
82                denominator,
83            }
84            .reduce(),
85        )
86    }
87
88    /// Convert the rational number into (numerator, denumerator) parts.
89    ///
90    /// # Examples
91    ///
92    /// ```
93    /// # use dashu_int::{IBig, UBig};
94    /// # use dashu_ratio::RBig;
95    /// assert_eq!(RBig::ZERO.into_parts(), (IBig::ZERO, UBig::ONE));
96    /// assert_eq!(RBig::ONE.into_parts(), (IBig::ONE, UBig::ONE));
97    /// assert_eq!(RBig::NEG_ONE.into_parts(), (IBig::NEG_ONE, UBig::ONE));
98    /// ```
99    #[inline]
100    pub fn into_parts(self) -> (IBig, UBig) {
101        (self.0.numerator, self.0.denominator)
102    }
103
104    /// Create a rational number from a signed numerator and a signed denominator
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// # use dashu_int::{IBig, UBig};
110    /// # use dashu_ratio::RBig;
111    /// assert_eq!(RBig::from_parts_signed(1.into(), 1.into()), RBig::ONE);
112    /// assert_eq!(RBig::from_parts_signed(12.into(), (-12).into()), RBig::NEG_ONE);
113    /// ```
114    #[inline]
115    pub fn from_parts_signed(numerator: IBig, denominator: IBig) -> Self {
116        let (sign, mag) = denominator.into_parts();
117        Self::from_parts(numerator * sign, mag)
118    }
119
120    /// Create a rational number in a const context
121    ///
122    /// The magnitude of the numerator and the denominator is limited to
123    /// a [dashu_int::DoubleWord].
124    ///
125    /// # Examples
126    ///
127    /// ```
128    /// # use dashu_int::Sign;
129    /// # use dashu_ratio::{RBig, Relaxed};
130    /// const ONE: RBig = RBig::from_parts_const(Sign::Positive, 1, 1);
131    /// assert_eq!(ONE, RBig::ONE);
132    /// const NEG_ONE: RBig = RBig::from_parts_const(Sign::Negative, 1, 1);
133    /// assert_eq!(NEG_ONE, RBig::NEG_ONE);
134    /// ```
135    #[inline]
136    pub const fn from_parts_const(
137        sign: Sign,
138        mut numerator: DoubleWord,
139        mut denominator: DoubleWord,
140    ) -> Self {
141        if denominator == 0 {
142            panic_divide_by_0()
143        } else if numerator == 0 {
144            return Self::ZERO;
145        }
146
147        if numerator > 1 && denominator > 1 {
148            // perform a naive but const gcd
149            let (mut y, mut r) = (denominator, numerator % denominator);
150            while r > 1 {
151                let new_r = y % r;
152                y = r;
153                r = new_r;
154            }
155            if r == 0 {
156                numerator /= y;
157                denominator /= y;
158            }
159        }
160
161        Self(Repr {
162            numerator: IBig::from_parts_const(sign, numerator),
163            denominator: UBig::from_dword(denominator),
164        })
165    }
166
167    /// Get the numerator of the rational number
168    ///
169    /// # Examples
170    ///
171    /// ```
172    /// # use dashu_int::IBig;
173    /// # use dashu_ratio::RBig;
174    /// assert_eq!(RBig::ZERO.numerator(), &IBig::ZERO);
175    /// assert_eq!(RBig::ONE.numerator(), &IBig::ONE);
176    /// ```
177    #[inline]
178    pub fn numerator(&self) -> &IBig {
179        &self.0.numerator
180    }
181
182    /// Get the denominator of the rational number
183    ///
184    /// # Examples
185    ///
186    /// ```
187    /// # use dashu_int::UBig;
188    /// # use dashu_ratio::RBig;
189    /// assert_eq!(RBig::ZERO.denominator(), &UBig::ONE);
190    /// assert_eq!(RBig::ONE.denominator(), &UBig::ONE);
191    /// ```
192    #[inline]
193    pub fn denominator(&self) -> &UBig {
194        &self.0.denominator
195    }
196
197    /// Convert this rational number into a [Relaxed] version
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// # use dashu_ratio::{RBig, Relaxed};
203    /// assert_eq!(RBig::ZERO.relax(), Relaxed::ZERO);
204    /// assert_eq!(RBig::ONE.relax(), Relaxed::ONE);
205    /// ```
206    #[inline]
207    pub fn relax(self) -> Relaxed {
208        Relaxed(self.0)
209    }
210
211    /// Regard the number as a [Relaxed] number and return a reference of [Relaxed] type.
212    ///
213    /// # Examples
214    ///
215    /// ```
216    /// # use dashu_ratio::{RBig, Relaxed};
217    /// assert_eq!(RBig::ONE.as_relaxed(), &Relaxed::ONE);
218    #[inline]
219    pub const fn as_relaxed(&self) -> &Relaxed {
220        // SAFETY: RBig and Relaxed are both transparent wrapper around the Repr type.
221        //         This conversion is only available for immutable references, so that
222        //         the rational number will be kept reduced.
223        unsafe { core::mem::transmute(self) }
224    }
225
226    /// Check whether the number is 0
227    ///
228    /// # Examples
229    ///
230    /// ```
231    /// # use dashu_ratio::RBig;
232    /// assert!(RBig::ZERO.is_zero());
233    /// assert!(!RBig::ONE.is_zero());
234    /// ```
235    #[inline]
236    pub const fn is_zero(&self) -> bool {
237        self.0.numerator.is_zero()
238    }
239
240    /// Check whether the number is 1
241    ///
242    /// # Examples
243    ///
244    /// ```
245    /// # use dashu_ratio::RBig;
246    /// assert!(!RBig::ZERO.is_one());
247    /// assert!(RBig::ONE.is_one());
248    /// ```
249    #[inline]
250    pub const fn is_one(&self) -> bool {
251        self.0.numerator.is_one() && self.0.denominator.is_one()
252    }
253
254    /// Determine if the number can be regarded as an integer.
255    ///
256    /// # Examples
257    ///
258    /// ```
259    /// # use dashu_ratio::RBig;
260    /// assert!(RBig::ZERO.is_int());
261    /// assert!(RBig::ONE.is_int());
262    /// ```
263    #[inline]
264    pub const fn is_int(&self) -> bool {
265        self.0.denominator.is_one()
266    }
267}
268
269// This custom implementation is necessary due to https://github.com/rust-lang/rust/issues/98374
270impl Clone for RBig {
271    #[inline]
272    fn clone(&self) -> RBig {
273        RBig(self.0.clone())
274    }
275    #[inline]
276    fn clone_from(&mut self, source: &RBig) {
277        self.0.clone_from(&source.0)
278    }
279}
280
281impl Default for RBig {
282    #[inline]
283    fn default() -> Self {
284        Self::ZERO
285    }
286}
287
288impl EstimatedLog2 for RBig {
289    #[inline]
290    fn log2_bounds(&self) -> (f32, f32) {
291        self.0.log2_bounds()
292    }
293    #[inline]
294    fn log2_est(&self) -> f32 {
295        self.0.log2_est()
296    }
297}
298
299impl Relaxed {
300    /// [Relaxed] with value 0
301    pub const ZERO: Self = Self(Repr::zero());
302    /// [Relaxed] with value 1
303    pub const ONE: Self = Self(Repr::one());
304    /// [Relaxed] with value -1
305    pub const NEG_ONE: Self = Self(Repr::neg_one());
306
307    /// Create a rational number from a signed numerator and a signed denominator
308    ///
309    /// See [RBig::from_parts] for details.
310    #[inline]
311    pub fn from_parts(numerator: IBig, denominator: UBig) -> Self {
312        if denominator.is_zero() {
313            panic_divide_by_0();
314        }
315
316        Self(
317            Repr {
318                numerator,
319                denominator,
320            }
321            .reduce2(),
322        )
323    }
324
325    /// Convert the rational number into (numerator, denumerator) parts.
326    ///
327    /// See [RBig::into_parts] for details.
328    #[inline]
329    pub fn into_parts(self) -> (IBig, UBig) {
330        (self.0.numerator, self.0.denominator)
331    }
332
333    /// Create a rational number from a signed numerator and a signed denominator
334    ///
335    /// See [RBig::from_parts_signed] for details.
336    #[inline]
337    pub fn from_parts_signed(numerator: IBig, denominator: IBig) -> Self {
338        let (sign, mag) = denominator.into_parts();
339        Self::from_parts(numerator * sign, mag)
340    }
341
342    /// Create a rational number in a const context
343    ///
344    /// See [RBig::from_parts_const] for details.
345    #[inline]
346    pub const fn from_parts_const(
347        sign: Sign,
348        numerator: DoubleWord,
349        denominator: DoubleWord,
350    ) -> Self {
351        if denominator == 0 {
352            panic_divide_by_0()
353        } else if numerator == 0 {
354            return Self::ZERO;
355        }
356
357        let n2 = numerator.trailing_zeros();
358        let d2 = denominator.trailing_zeros();
359        let zeros = if n2 <= d2 { n2 } else { d2 };
360        Self(Repr {
361            numerator: IBig::from_parts_const(sign, numerator >> zeros),
362            denominator: UBig::from_dword(denominator >> zeros),
363        })
364    }
365
366    /// Create an Relaxed instance from two static sequences of [Word][crate::Word]s representing the
367    /// numerator and denominator.
368    ///
369    /// This method is intended for static creation macros.
370    #[doc(hidden)]
371    #[inline]
372    pub const unsafe fn from_static_words(
373        sign: dashu_base::Sign,
374        numerator_words: &'static [dashu_int::Word],
375        denominator_words: &'static [dashu_int::Word],
376    ) -> Self {
377        Self(Repr::from_static_words(sign, numerator_words, denominator_words))
378    }
379
380    /// Get the numerator of the rational number
381    ///
382    /// See [RBig::numerator] for details.
383    #[inline]
384    pub fn numerator(&self) -> &IBig {
385        &self.0.numerator
386    }
387
388    /// Get the denominator of the rational number
389    ///
390    /// See [RBig::denominator] for details.
391    #[inline]
392    pub fn denominator(&self) -> &UBig {
393        &self.0.denominator
394    }
395
396    /// Convert this rational number into an [RBig] version
397    ///
398    /// # Examples
399    ///
400    /// ```
401    /// # use dashu_int::IBig;
402    /// # use dashu_ratio::{RBig, Relaxed};
403    /// assert_eq!(Relaxed::ONE.canonicalize(), RBig::ONE);
404    ///
405    /// let r = Relaxed::from_parts(10.into(), 5u8.into());
406    /// assert_eq!(r.canonicalize().numerator(), &IBig::from(2));
407    /// ```
408    #[inline]
409    pub fn canonicalize(self) -> RBig {
410        RBig(self.0.reduce())
411    }
412
413    /// Check whether the number is 0
414    ///
415    /// See [RBig::is_zero] for details.
416    #[inline]
417    pub const fn is_zero(&self) -> bool {
418        self.0.numerator.is_zero()
419    }
420
421    /// Check whether the number is 1
422    ///
423    /// See [RBig::is_one] for details.
424    #[inline]
425    pub fn is_one(&self) -> bool {
426        self.0.denominator.as_ibig() == &self.0.numerator
427    }
428}
429
430// This custom implementation is necessary due to https://github.com/rust-lang/rust/issues/98374
431impl Clone for Relaxed {
432    #[inline]
433    fn clone(&self) -> Relaxed {
434        Relaxed(self.0.clone())
435    }
436    #[inline]
437    fn clone_from(&mut self, source: &Relaxed) {
438        self.0.clone_from(&source.0)
439    }
440}
441
442impl Default for Relaxed {
443    #[inline]
444    fn default() -> Self {
445        Self::ZERO
446    }
447}
448
449impl EstimatedLog2 for Relaxed {
450    #[inline]
451    fn log2_bounds(&self) -> (f32, f32) {
452        self.0.log2_bounds()
453    }
454    #[inline]
455    fn log2_est(&self) -> f32 {
456        self.0.log2_est()
457    }
458}