rug/rational/small.rs
1// Copyright © 2016–2025 Trevor Spiteri
2
3// This program is free software: you can redistribute it and/or modify it under
4// the terms of the GNU Lesser General Public License as published by the Free
5// Software Foundation, either version 3 of the License, or (at your option) any
6// later version.
7//
8// This program is distributed in the hope that it will be useful, but WITHOUT
9// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
11// details.
12//
13// You should have received a copy of the GNU Lesser General Public License and
14// a copy of the GNU General Public License along with this program. If not, see
15// <https://www.gnu.org/licenses/>.
16
17#![allow(deprecated)]
18
19use crate::integer::{MiniInteger, ToSmall};
20use crate::{Assign, Rational};
21use core::fmt::{Debug, Formatter, Result as FmtResult};
22use core::marker::PhantomData;
23use core::ops::Deref;
24use gmp_mpfr_sys::gmp::limb_t;
25
26/**
27A small rational number that did not require any memory allocation until version 1.23.0.
28
29Because of a [soundness issue], this has been deprecated and replaced by
30[`MiniRational`]. To fix the soundness issue, this struct now uses allocations
31like [`Rational`] itself, so it is less efficient than [`MiniRational`].
32
33The `SmallRational` type can be coerced to a [`Rational`], as it implements
34<code>[Deref]\<[Target][Deref::Target] = [Rational]></code>.
35
36# Examples
37
38```rust
39#![allow(deprecated)]
40
41use rug::rational::SmallRational;
42use rug::Rational;
43// `a` requires a heap allocation
44let mut a = Rational::from((100, 13));
45// `b` can reside on the stack
46let b = SmallRational::from((-100, 21));
47a /= &*b;
48assert_eq!(*a.numer(), -21);
49assert_eq!(*a.denom(), 13);
50```
51
52[`MiniRational`]: crate::rational::MiniRational
53[soundness issue]: https://gitlab.com/tspiteri/rug/-/issues/52
54*/
55#[deprecated(since = "1.23.0", note = "use `MiniRational` instead")]
56#[derive(Clone)]
57pub struct SmallRational {
58 inner: Option<Rational>,
59 // for !Sync
60 phantom: PhantomData<*const limb_t>,
61}
62
63unsafe impl Send for SmallRational {}
64
65impl Default for SmallRational {
66 #[inline]
67 fn default() -> Self {
68 SmallRational::new()
69 }
70}
71
72impl Debug for SmallRational {
73 #[inline]
74 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
75 match &self.inner {
76 Some(r) => Debug::fmt(r, f),
77 None => Debug::fmt(Rational::ZERO, f),
78 }
79 }
80}
81
82impl SmallRational {
83 /// Creates a [`SmallRational`] with value 0.
84 ///
85 /// # Examples
86 ///
87 /// ```rust
88 /// #![allow(deprecated)]
89 ///
90 /// use rug::rational::SmallRational;
91 /// let r = SmallRational::new();
92 /// // Use r as if it were Rational.
93 /// assert_eq!(*r.numer(), 0);
94 /// assert_eq!(*r.denom(), 1);
95 /// ```
96 #[inline]
97 pub const fn new() -> Self {
98 SmallRational {
99 inner: None,
100 phantom: PhantomData,
101 }
102 }
103
104 /// Returns a mutable reference to a [`Rational`] number for simple
105 /// operations that do not need to allocate more space for the numerator or
106 /// denominator.
107 ///
108 /// # Safety
109 ///
110 /// It is undefined behavior to perform operations that reallocate the
111 /// internal data of the referenced [`Rational`] number or to swap it with
112 /// another number, although it is allowed to swap the numerator and
113 /// denominator allocations, such as in the reciprocal operation
114 /// [`recip_mut`].
115 ///
116 /// Some GMP functions swap the allocations of their target operands;
117 /// calling such functions with the mutable reference returned by this
118 /// method can lead to undefined behavior.
119 ///
120 /// # Examples
121 ///
122 /// ```rust
123 /// #![allow(deprecated)]
124 ///
125 /// use rug::rational::SmallRational;
126 /// let mut r = SmallRational::from((-15i32, 47i32));
127 /// let num_capacity = r.numer().capacity();
128 /// let den_capacity = r.denom().capacity();
129 /// // reciprocating this will not require reallocations
130 /// unsafe {
131 /// r.as_nonreallocating_rational().recip_mut();
132 /// }
133 /// assert_eq!(*r, SmallRational::from((-47, 15)));
134 /// assert_eq!(r.numer().capacity(), num_capacity);
135 /// assert_eq!(r.denom().capacity(), den_capacity);
136 /// ```
137 ///
138 /// [`recip_mut`]: `Rational::recip_mut`
139 #[inline]
140 pub unsafe fn as_nonreallocating_rational(&mut self) -> &mut Rational {
141 if self.inner.is_none() {
142 *self = SmallRational {
143 inner: Some(Rational::new()),
144 phantom: PhantomData,
145 };
146 }
147 match &mut self.inner {
148 Some(r) => r,
149 None => unreachable!(),
150 }
151 }
152
153 /// Creates a [`SmallRational`] from a numerator and denominator, assuming
154 /// they are in canonical form.
155 ///
156 /// # Safety
157 ///
158 /// This method leads to undefined behavior if `den` is zero or if `num` and
159 /// `den` have common factors.
160 ///
161 /// # Examples
162 ///
163 /// ```rust
164 /// #![allow(deprecated)]
165 ///
166 /// use rug::rational::SmallRational;
167 /// let from_unsafe = unsafe { SmallRational::from_canonical(-13, 10) };
168 /// // from_safe is canonicalized to the same form as from_unsafe
169 /// let from_safe = SmallRational::from((130, -100));
170 /// assert_eq!(from_unsafe.numer(), from_safe.numer());
171 /// assert_eq!(from_unsafe.denom(), from_safe.denom());
172 /// ```
173 pub unsafe fn from_canonical<Num: ToSmall, Den: ToSmall>(num: Num, den: Den) -> Self {
174 let num = MiniInteger::from(num);
175 let den = MiniInteger::from(den);
176 SmallRational {
177 inner: Some(unsafe { Rational::from_canonical(num, den) }),
178 phantom: PhantomData,
179 }
180 }
181
182 /// Assigns a numerator and denominator to a [`SmallRational`], assuming
183 /// they are in canonical form.
184 ///
185 /// # Safety
186 ///
187 /// This method leads to undefined behavior if `den` is zero or negative, or
188 /// if `num` and `den` have common factors.
189 ///
190 /// # Examples
191 ///
192 /// ```rust
193 /// #![allow(deprecated)]
194 ///
195 /// use rug::rational::SmallRational;
196 /// use rug::Assign;
197 /// let mut a = SmallRational::new();
198 /// unsafe {
199 /// a.assign_canonical(-13, 10);
200 /// }
201 /// // b is canonicalized to the same form as a
202 /// let mut b = SmallRational::new();
203 /// b.assign((130, -100));
204 /// assert_eq!(a.numer(), b.numer());
205 /// assert_eq!(a.denom(), b.denom());
206 /// ```
207 pub unsafe fn assign_canonical<Num: ToSmall, Den: ToSmall>(&mut self, num: Num, den: Den) {
208 let mut num = MiniInteger::from(num);
209 let mut den = MiniInteger::from(den);
210 unsafe {
211 self.as_nonreallocating_rational()
212 .assign_canonical(num.borrow_excl(), den.borrow_excl());
213 }
214 }
215}
216
217impl Deref for SmallRational {
218 type Target = Rational;
219 #[inline]
220 fn deref(&self) -> &Rational {
221 match &self.inner {
222 Some(r) => r,
223 None => Rational::ZERO,
224 }
225 }
226}
227
228impl<Num: ToSmall> Assign<Num> for SmallRational {
229 #[inline]
230 fn assign(&mut self, src: Num) {
231 let mut mini = MiniInteger::from(src);
232 unsafe {
233 self.as_nonreallocating_rational()
234 .assign(mini.borrow_excl());
235 }
236 }
237}
238
239impl<Num: ToSmall> From<Num> for SmallRational {
240 fn from(src: Num) -> Self {
241 let mut mini = MiniInteger::from(src);
242 SmallRational {
243 inner: Some(Rational::from(mini.borrow_excl())),
244 phantom: PhantomData,
245 }
246 }
247}
248
249impl<Num: ToSmall, Den: ToSmall> Assign<(Num, Den)> for SmallRational {
250 fn assign(&mut self, src: (Num, Den)) {
251 assert!(!src.1.is_zero(), "division by zero");
252 let mut num = MiniInteger::from(src.0);
253 let mut den = MiniInteger::from(src.1);
254 unsafe {
255 self.as_nonreallocating_rational()
256 .assign((num.borrow_excl(), den.borrow_excl()));
257 }
258 }
259}
260
261impl<Num: ToSmall, Den: ToSmall> From<(Num, Den)> for SmallRational {
262 fn from(src: (Num, Den)) -> Self {
263 assert!(!src.1.is_zero(), "division by zero");
264 let mut num = MiniInteger::from(src.0);
265 let mut den = MiniInteger::from(src.1);
266 SmallRational {
267 inner: Some(Rational::from((num.borrow_excl(), den.borrow_excl()))),
268 phantom: PhantomData,
269 }
270 }
271}
272
273impl Assign<&Self> for SmallRational {
274 #[inline]
275 fn assign(&mut self, other: &Self) {
276 self.clone_from(other);
277 }
278}
279
280impl Assign for SmallRational {
281 #[inline]
282 fn assign(&mut self, other: Self) {
283 *self = other;
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use crate::Assign;
290 use crate::rational::SmallRational;
291
292 #[test]
293 fn check_assign() {
294 let mut r = SmallRational::from((1, 2));
295 assert_eq!(*r, SmallRational::from((1, 2)));
296 r.assign(3);
297 assert_eq!(*r, 3);
298 let other = SmallRational::from((4, 5));
299 r.assign(&other);
300 assert_eq!(*r, SmallRational::from((4, 5)));
301 r.assign((6, 7));
302 assert_eq!(*r, SmallRational::from((6, 7)));
303 r.assign(other);
304 assert_eq!(*r, SmallRational::from((4, 5)));
305 }
306
307 fn swapped_parts(small: &SmallRational) -> bool {
308 unsafe {
309 let num = (*small.numer().as_raw()).d;
310 let den = (*small.denom().as_raw()).d;
311 num > den
312 }
313 }
314
315 #[test]
316 fn check_swapped_parts() {
317 let mut r = SmallRational::from((2, 3));
318 assert_eq!(*r, SmallRational::from((2, 3)));
319 assert_eq!(*r.clone(), *r);
320 let mut orig_swapped_parts = swapped_parts(&r);
321 unsafe {
322 r.as_nonreallocating_rational().recip_mut();
323 }
324 assert_eq!(*r, SmallRational::from((3, 2)));
325 assert_eq!(*r.clone(), *r);
326 assert!(swapped_parts(&r) != orig_swapped_parts);
327
328 unsafe {
329 r.assign_canonical(5, 7);
330 }
331 assert_eq!(*r, SmallRational::from((5, 7)));
332 assert_eq!(*r.clone(), *r);
333 orig_swapped_parts = swapped_parts(&r);
334 unsafe {
335 r.as_nonreallocating_rational().recip_mut();
336 }
337 assert_eq!(*r, SmallRational::from((7, 5)));
338 assert_eq!(*r.clone(), *r);
339 assert!(swapped_parts(&r) != orig_swapped_parts);
340
341 r.assign(2);
342 assert_eq!(*r, 2);
343 assert_eq!(*r.clone(), *r);
344 orig_swapped_parts = swapped_parts(&r);
345 unsafe {
346 r.as_nonreallocating_rational().recip_mut();
347 }
348 assert_eq!(*r, SmallRational::from((1, 2)));
349 assert_eq!(*r.clone(), *r);
350 assert!(swapped_parts(&r) != orig_swapped_parts);
351
352 r.assign((3, -5));
353 assert_eq!(*r, SmallRational::from((-3, 5)));
354 assert_eq!(*r.clone(), *r);
355 orig_swapped_parts = swapped_parts(&r);
356 unsafe {
357 r.as_nonreallocating_rational().recip_mut();
358 }
359 assert_eq!(*r, SmallRational::from((-5, 3)));
360 assert_eq!(*r.clone(), *r);
361 assert!(swapped_parts(&r) != orig_swapped_parts);
362 }
363}