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