1use num_bigint::BigInt;
12use num_integer::Integer;
13use num_traits::{One, Signed, ToPrimitive, Zero};
14
15use crate::basis::Basis;
16use crate::primes::{distinct_primes, radical, termination_period};
17use crate::rns::RnsInt;
18
19#[derive(Clone, Debug)]
28pub struct RnsRational {
29 p: BigInt,
31 q: BigInt,
33 pub numer: RnsInt,
35 pub denom: RnsInt,
37 pub channels: Basis,
38}
39
40impl RnsRational {
41 pub fn new(p: BigInt, q: BigInt, channels: Basis) -> Self {
45 assert!(!q.is_zero(), "denominator must be non-zero");
46 let mut p = p;
47 let mut q = q;
48 if q.is_negative() {
49 p = -p;
50 q = -q;
51 }
52 let g = p.gcd(&q);
53 if !g.is_zero() {
54 p /= &g;
55 q /= &g;
56 }
57 let numer = RnsInt::from_bigint(&p, channels.clone());
58 let denom = RnsInt::from_bigint(&q, channels.clone());
59 RnsRational {
60 p,
61 q,
62 numer,
63 denom,
64 channels,
65 }
66 }
67
68 pub fn from_fraction(p: i64, q: i64, channels: Basis) -> Self {
70 Self::new(BigInt::from(p), BigInt::from(q), channels)
71 }
72
73 pub fn from_int(n: i64, channels: Basis) -> Self {
75 Self::from_fraction(n, 1, channels)
76 }
77
78 pub fn zero(channels: Basis) -> Self {
80 Self::from_fraction(0, 1, channels)
81 }
82
83 pub fn to_pair(&self) -> (BigInt, BigInt) {
85 (self.p.clone(), self.q.clone())
86 }
87
88 pub fn is_zero(&self) -> bool {
90 self.p.is_zero()
91 }
92
93 pub fn is_integer(&self) -> bool {
95 self.q == BigInt::one()
96 }
97
98 fn op(&self, other: &Self, f: impl Fn(&BigInt, &BigInt, &BigInt, &BigInt) -> (BigInt, BigInt)) -> Self {
99 let (p1, q1) = self.to_pair();
100 let (p2, q2) = other.to_pair();
101 let (p, q) = f(&p1, &q1, &p2, &q2);
102 Self::new(p, q, self.channels.clone())
103 }
104
105 pub fn add(&self, other: &Self) -> Self {
107 self.op(other, |p1, q1, p2, q2| (p1 * q2 + p2 * q1, q1 * q2))
108 }
109
110 pub fn sub(&self, other: &Self) -> Self {
112 self.op(other, |p1, q1, p2, q2| (p1 * q2 - p2 * q1, q1 * q2))
113 }
114
115 pub fn mul(&self, other: &Self) -> Self {
117 self.op(other, |p1, q1, p2, q2| (p1 * p2, q1 * q2))
118 }
119
120 pub fn div(&self, other: &Self) -> Self {
122 assert!(!other.is_zero(), "division by zero");
123 self.op(other, |p1, q1, p2, q2| (p1 * q2, q1 * p2))
124 }
125
126 pub fn neg(&self) -> Self {
128 let (p, q) = self.to_pair();
129 Self::new(-p, q, self.channels.clone())
130 }
131
132 pub fn recip(&self) -> Self {
134 assert!(!self.is_zero(), "reciprocal of zero");
135 let (p, q) = self.to_pair();
136 Self::new(q, p, self.channels.clone())
137 }
138
139 fn denom_u64(&self) -> Option<u64> {
144 self.denom.to_bigint().to_u64()
145 }
146
147 pub fn denom_prime_signature(&self) -> Vec<u64> {
149 match self.denom_u64() {
150 Some(d) => distinct_primes(d),
151 None => Vec::new(),
152 }
153 }
154
155 pub fn exact_in_base(&self, base: u64) -> bool {
158 self.denom_prime_signature()
159 .into_iter()
160 .all(|p| base % p == 0)
161 }
162
163 pub fn natural_base(&self) -> u64 {
165 self.denom_u64().map(radical).unwrap_or(1).max(1)
166 }
167
168 pub fn termination_period_in_base(&self, base: u64) -> u64 {
170 match self.denom_u64() {
171 Some(d) => termination_period(d, base).unwrap_or(0),
172 None => 0,
173 }
174 }
175
176 pub fn to_f64(&self) -> f64 {
183 let (p, q) = self.to_pair();
184 ratio_to_f64(&p, &q)
185 }
186
187 pub fn from_f64(x: f64, channels: Basis) -> Self {
189 if x == 0.0 || !x.is_finite() {
190 return Self::zero(channels);
191 }
192 let bits = x.to_bits();
193 let sign = if bits >> 63 == 1 { -1i64 } else { 1 };
194 let exponent = ((bits >> 52) & 0x7ff) as i64;
195 let mantissa = bits & 0x000f_ffff_ffff_ffff;
196 let (m, e) = if exponent == 0 {
198 (mantissa, -1074i64) } else {
200 (mantissa | 0x0010_0000_0000_0000, exponent - 1075)
201 };
202 let m = BigInt::from(sign) * BigInt::from(m);
203 if e >= 0 {
204 Self::new(m * (BigInt::one() << (e as usize)), BigInt::one(), channels)
205 } else {
206 Self::new(m, BigInt::one() << ((-e) as usize), channels)
207 }
208 }
209
210 pub fn to_f64_with_error(&self) -> (f64, RnsRational) {
213 let approx = self.to_f64();
214 let approx_exact = Self::from_f64(approx, self.channels.clone());
215 let error = self.sub(&approx_exact);
216 (approx, error)
217 }
218
219 pub fn signum(&self) -> i32 {
221 match self.p.sign() {
222 num_bigint::Sign::Minus => -1,
223 num_bigint::Sign::NoSign => 0,
224 num_bigint::Sign::Plus => 1,
225 }
226 }
227
228 pub fn abs(&self) -> Self {
230 if self.signum() < 0 {
231 self.neg()
232 } else {
233 self.clone()
234 }
235 }
236
237 pub fn midpoint(&self, other: &Self) -> Self {
239 self.add(other).mul(&Self::from_fraction(1, 2, self.channels.clone()))
240 }
241
242 pub fn display(&self) -> String {
244 let (p, q) = self.to_pair();
245 if q == BigInt::one() {
246 p.to_string()
247 } else {
248 format!("{p}/{q}")
249 }
250 }
251}
252
253fn ratio_to_f64(p: &BigInt, q: &BigInt) -> f64 {
256 if q.is_zero() {
257 return f64::NAN;
258 }
259 if p.is_zero() {
260 return 0.0;
261 }
262 let negative = (p.sign() == num_bigint::Sign::Minus) ^ (q.sign() == num_bigint::Sign::Minus);
263 let mut pm = p.magnitude().clone();
264 let mut qm = q.magnitude().clone();
265 let pb = pm.bits() as i64;
266 let qb = qm.bits() as i64;
267 let shift = 60 + qb - pb;
269 if shift > 0 {
270 pm <<= shift as u64;
271 } else {
272 qm <<= (-shift) as u64;
273 }
274 let quo = &pm / &qm;
275 let mag = quo.to_f64().unwrap_or(f64::INFINITY);
276 let value = mag * 2f64.powi(-(shift as i32));
277 if negative {
278 -value
279 } else {
280 value
281 }
282}
283
284impl PartialEq for RnsRational {
285 fn eq(&self, other: &Self) -> bool {
286 self.to_pair() == other.to_pair()
287 }
288}
289impl Eq for RnsRational {}
290
291impl PartialOrd for RnsRational {
292 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
293 Some(self.cmp(other))
294 }
295}
296
297impl Ord for RnsRational {
298 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
299 let (p1, q1) = self.to_pair();
301 let (p2, q2) = other.to_pair();
302 (p1 * q2).cmp(&(p2 * q1))
303 }
304}
305
306impl std::fmt::Display for RnsRational {
307 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308 f.write_str(&self.display())
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315
316 fn ch() -> Basis {
317 Basis::standard()
318 }
319
320 fn frac(p: i64, q: i64) -> RnsRational {
321 RnsRational::from_fraction(p, q, ch())
322 }
323
324 #[test]
325 fn sixths_tenths_fifteenths() {
326 let r = frac(1, 6).add(&frac(1, 10)).add(&frac(1, 15));
328 assert_eq!(r, frac(1, 3));
329 }
330
331 #[test]
332 fn third_times_three() {
333 assert_eq!(frac(1, 3).mul(&frac(3, 1)), frac(1, 1));
334 assert_eq!(frac(1, 7).mul(&frac(7, 1)), frac(1, 1));
335 }
336
337 #[test]
338 fn point_one_plus_point_two() {
339 assert_eq!(frac(1, 10).add(&frac(1, 5)), frac(3, 10));
341 }
342
343 #[test]
344 fn eighths() {
345 assert_eq!(frac(7, 8).sub(&frac(3, 8)), frac(1, 2));
346 }
347
348 #[test]
349 fn base_awareness() {
350 assert_eq!(frac(1, 6).natural_base(), 6);
351 assert_eq!(frac(1, 12).natural_base(), 6); assert!(frac(1, 6).exact_in_base(6));
353 assert!(!frac(1, 6).exact_in_base(10));
354 assert!(frac(1, 6).exact_in_base(30));
355 assert_eq!(frac(1, 8).termination_period_in_base(10), 0); assert_eq!(frac(1, 3).termination_period_in_base(10), 1);
357 }
358
359 #[test]
360 fn f64_error_is_exact() {
361 let (approx, err) = frac(1, 10).to_f64_with_error();
363 assert!((approx - 0.1).abs() < 1e-17);
364 assert!(!err.is_zero());
365 let reconstructed = RnsRational::from_f64(approx, ch()).add(&err);
367 assert_eq!(reconstructed, frac(1, 10));
368 }
369
370 #[test]
371 fn display_form() {
372 assert_eq!(frac(3, 7).display(), "3/7");
373 assert_eq!(frac(10, 2).display(), "5");
374 }
375}