1use crate::defs::RoundingMode;
4use crate::defs::DEFAULT_P;
5use crate::defs::WORD_BIT_SIZE;
6use crate::ExactInt;
7use crate::ExactNum;
8use crate::Radix;
9use crate::NAN;
10use alloc::string::String;
11use alloc::vec::Vec;
12use core::cmp::Ordering;
13
14#[derive(Clone, Debug)]
20pub struct ExactRational {
21 num: ExactNum,
22 den: ExactNum,
23}
24
25fn rat_one() -> ExactNum {
26 ExactNum::from_u8(1, DEFAULT_P)
27}
28
29fn work_p(xs: &[&ExactNum]) -> usize {
30 let mut p = DEFAULT_P;
31 for x in xs {
32 if let Some(px) = x.precision() {
33 p = p.max(px);
34 }
35 }
36 p.saturating_add(WORD_BIT_SIZE)
37}
38
39fn is_int(x: &ExactNum) -> bool {
40 if x.is_nan() || x.is_inf() {
41 return false;
42 }
43 x.fract().is_zero()
44}
45
46fn gcd_int(mut a: ExactNum, mut b: ExactNum) -> ExactNum {
47 a = a.abs();
48 b = b.abs();
49 while !b.is_zero() {
50 let r = a.rem(&b);
51 a = b;
52 b = r;
53 }
54 if a.is_zero() {
55 rat_one()
56 } else {
57 a
58 }
59}
60
61fn reduce(mut num: ExactNum, mut den: ExactNum) -> ExactRational {
62 if num.is_nan() || den.is_nan() || num.is_inf() || den.is_inf() || den.is_zero() {
63 return ExactRational {
64 num: NAN,
65 den: rat_one(),
66 };
67 }
68 if den.is_negative() {
69 num = num.neg();
70 den = den.neg();
71 }
72 if is_int(&num) && is_int(&den) {
73 let g = gcd_int(num.clone(), den.clone());
74 if !g.is_zero() && g.cmp(&rat_one()) != Some(0) {
75 let p = work_p(&[&num, &den, &g]);
76 num = num.div(&g, p, RoundingMode::ToEven);
77 den = den.div(&g, p, RoundingMode::ToEven);
78 }
79 }
80 ExactRational { num, den }
81}
82
83impl ExactRational {
84 pub fn new(num: ExactNum, den: ExactNum) -> Self {
87 reduce(num, den)
88 }
89
90 pub fn from_i64(n: i64, d: i64) -> Self {
92 Self::new(
93 ExactNum::from_i64(n, DEFAULT_P),
94 ExactNum::from_i64(d, DEFAULT_P),
95 )
96 }
97
98 pub fn num(&self) -> &ExactNum {
100 &self.num
101 }
102
103 pub fn den(&self) -> &ExactNum {
105 &self.den
106 }
107
108 pub fn is_nan(&self) -> bool {
110 self.num.is_nan() || self.den.is_nan()
111 }
112
113 pub fn is_integer(&self) -> bool {
115 !self.is_nan() && self.den.cmp(&rat_one()) == Some(0)
116 }
117
118 pub fn add(&self, rhs: &Self) -> Self {
120 if self.is_nan() || rhs.is_nan() {
121 return Self::new(NAN, rat_one());
122 }
123 let p = work_p(&[&self.num, &self.den, &rhs.num, &rhs.den]);
124 let rm = RoundingMode::None;
125 let ad = self.num.mul(&rhs.den, p, rm);
126 let bc = rhs.num.mul(&self.den, p, rm);
127 let num = ad.add(&bc, p, rm);
128 let den = self.den.mul(&rhs.den, p, rm);
129 Self::new(num, den)
130 }
131
132 pub fn sub(&self, rhs: &Self) -> Self {
134 self.add(&Self::new(rhs.num.neg(), rhs.den.clone()))
135 }
136
137 pub fn mul(&self, rhs: &Self) -> Self {
139 if self.is_nan() || rhs.is_nan() {
140 return Self::new(NAN, rat_one());
141 }
142 let p = work_p(&[&self.num, &self.den, &rhs.num, &rhs.den]);
143 let rm = RoundingMode::None;
144 Self::new(self.num.mul(&rhs.num, p, rm), self.den.mul(&rhs.den, p, rm))
145 }
146
147 pub fn div(&self, rhs: &Self) -> Self {
149 if self.is_nan() || rhs.is_nan() || rhs.num.is_zero() {
150 return Self::new(NAN, rat_one());
151 }
152 let p = work_p(&[&self.num, &self.den, &rhs.num, &rhs.den]);
153 let rm = RoundingMode::None;
154 Self::new(self.num.mul(&rhs.den, p, rm), self.den.mul(&rhs.num, p, rm))
155 }
156
157 pub fn to_exact_num(&self, p: usize, rm: RoundingMode) -> ExactNum {
159 if self.is_nan() {
160 return NAN;
161 }
162 self.num.div(&self.den, p, rm)
163 }
164
165 fn quot(&self) -> ExactNum {
166 let p = work_p(&[&self.num, &self.den]);
167 self.num.div(&self.den, p, RoundingMode::None)
168 }
169
170 pub fn floor(&self) -> Self {
172 if self.is_nan() {
173 return Self::new(NAN, rat_one());
174 }
175 Self::new(self.quot().floor(), rat_one())
176 }
177
178 pub fn ceil(&self) -> Self {
180 if self.is_nan() {
181 return Self::new(NAN, rat_one());
182 }
183 Self::new(self.quot().ceil(), rat_one())
184 }
185
186 pub fn round(&self) -> Self {
188 if self.is_nan() {
189 return Self::new(NAN, rat_one());
190 }
191 Self::new(self.quot().round(0, RoundingMode::FromZero), rat_one())
192 }
193
194 pub fn from_ints(num: ExactInt, den: ExactInt) -> Self {
196 if den.is_zero() {
197 return Self::new(NAN, rat_one());
198 }
199 let p = num
200 .bit_length()
201 .max(den.bit_length())
202 .max(DEFAULT_P)
203 .saturating_add(WORD_BIT_SIZE);
204 Self::new(
205 num.to_exact_num(p, RoundingMode::None),
206 den.to_exact_num(p, RoundingMode::None),
207 )
208 }
209
210 pub fn parse_exact(s: &str) -> Option<Self> {
214 let s = s.trim();
215 if s.is_empty() {
216 return None;
217 }
218 let (neg, rest) = match s.as_bytes()[0] {
219 b'+' => (false, &s[1..]),
220 b'-' => (true, &s[1..]),
221 _ => (false, s),
222 };
223 if rest.is_empty() {
224 return None;
225 }
226 let (mant, exp) = split_dec_exp(rest)?;
227 let (int_part, frac_part) = split_dot(mant);
228 if int_part.is_empty() && frac_part.is_empty() {
229 return None;
230 }
231 if !int_part.bytes().all(|c| c.is_ascii_digit())
232 || !frac_part.bytes().all(|c| c.is_ascii_digit())
233 {
234 return None;
235 }
236 let mut digits = String::new();
237 digits.push_str(int_part);
238 digits.push_str(frac_part);
239 if digits.is_empty() || digits.bytes().all(|c| c == b'0') {
240 return Some(Self::from_i64(0, 1));
241 }
242 let num = dec_digits_to_int(&digits)?;
243 let exp_adj = exp - frac_part.len() as i32;
244 let ten = ExactInt::from_i64(10);
245 let (n, d) = if exp_adj >= 0 {
246 (num.mul(&ten.pow(exp_adj as u64)), ExactInt::one())
247 } else {
248 (num, ten.pow((-exp_adj) as u64))
249 };
250 let mut r = Self::from_ints(n, d);
251 if neg {
252 r = Self::new(r.num.neg(), r.den.clone());
253 }
254 Some(r)
255 }
256
257 pub fn format_exact(&self, rdx: Radix) -> Option<String> {
260 if self.is_nan() {
261 return None;
262 }
263 let num = ExactInt::from_exact_num(&self.num)?;
264 let den = ExactInt::from_exact_num(&self.den)?;
265 format_terminating(&num, &den, rdx)
266 }
267
268 pub fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
270 if self.is_nan() || other.is_nan() {
271 return None;
272 }
273 let p = work_p(&[&self.num, &self.den, &other.num, &other.den]);
274 let rm = RoundingMode::None;
275 let left = self.num.mul(&other.den, p, rm);
276 let right = other.num.mul(&self.den, p, rm);
277 match left.cmp(&right) {
278 Some(c) if c > 0 => Some(Ordering::Greater),
279 Some(c) if c < 0 => Some(Ordering::Less),
280 Some(_) => Some(Ordering::Equal),
281 None => None,
282 }
283 }
284}
285
286fn split_dec_exp(s: &str) -> Option<(&str, i32)> {
287 let bytes = s.as_bytes();
288 let mut epos = None;
289 for (i, c) in bytes.iter().enumerate() {
290 if *c == b'e' || *c == b'E' {
291 epos = Some(i);
292 break;
293 }
294 }
295 match epos {
296 None => Some((s, 0)),
297 Some(i) => {
298 if i == 0 {
299 return None;
300 }
301 let exp_s = &s[i + 1..];
302 if exp_s.is_empty() {
303 return None;
304 }
305 let exp: i32 = exp_s.parse().ok()?;
306 Some((&s[..i], exp))
307 }
308 }
309}
310
311fn split_dot(s: &str) -> (&str, &str) {
312 match s.find('.') {
313 Some(i) => (&s[..i], &s[i + 1..]),
314 None => (s, ""),
315 }
316}
317
318fn dec_digits_to_int(s: &str) -> Option<ExactInt> {
319 let ten = ExactInt::from_i64(10);
320 let mut v = ExactInt::zero();
321 for c in s.bytes() {
322 if !c.is_ascii_digit() {
323 return None;
324 }
325 v = v.mul(&ten).add(&ExactInt::from_u64((c - b'0') as u64));
326 }
327 Some(v)
328}
329
330fn format_terminating(num: &ExactInt, den: &ExactInt, rdx: Radix) -> Option<String> {
331 if den.is_zero() {
332 return None;
333 }
334 let radix = ExactInt::from_u64(rdx.value() as u64);
335 let mut pk = ExactInt::one();
336 let max_k = den.bit_length().saturating_add(8);
337 for k in 0..=max_k {
338 if let Some((scale, rem)) = pk.div_rem(den) {
339 if rem.is_zero() {
340 let digits = num.mul(&scale);
341 return Some(format_fixed(&digits, k, rdx));
342 }
343 }
344 pk = pk.mul(&radix);
345 }
346 None
347}
348
349fn format_fixed(n: &ExactInt, frac_digits: usize, rdx: Radix) -> String {
350 let neg = n.is_negative();
351 let abs = if neg { n.neg() } else { n.clone() };
352 let mut digits = to_radix_digits(&abs, rdx);
353 let mut k = frac_digits;
354 while k > 0 && digits.ends_with('0') {
355 digits.pop();
356 k -= 1;
357 }
358 if digits.is_empty() {
359 digits.push('0');
360 }
361 let mut out = String::new();
362 if neg && digits != "0" {
363 out.push('-');
364 }
365 if k == 0 {
366 out.push_str(&digits);
367 return out;
368 }
369 if digits.len() <= k {
370 out.push('0');
371 out.push('.');
372 for _ in 0..(k - digits.len()) {
373 out.push('0');
374 }
375 out.push_str(&digits);
376 } else {
377 let split = digits.len() - k;
378 out.push_str(&digits[..split]);
379 out.push('.');
380 out.push_str(&digits[split..]);
381 }
382 out
383}
384
385fn to_radix_digits(n: &ExactInt, rdx: Radix) -> String {
386 if n.is_zero() {
387 return String::from("0");
388 }
389 let r = ExactInt::from_u64(rdx.value() as u64);
390 let mut v = if n.is_negative() { n.neg() } else { n.clone() };
391 let mut digits = Vec::new();
392 while !v.is_zero() {
393 let (q, rem) = v
394 .div_rem(&r)
395 .unwrap_or((ExactInt::zero(), ExactInt::zero()));
396 let d = rem.low_word() as u8;
397 digits.push(if d < 10 { b'0' + d } else { b'a' + (d - 10) });
398 v = q;
399 }
400 digits.reverse();
401 String::from_utf8(digits).unwrap_or_else(|_| String::from("0"))
402}
403
404fn exact_num_to_rational(x: &ExactNum) -> Option<ExactRational> {
405 if x.is_nan() || x.is_inf() {
406 return None;
407 }
408 if x.is_zero() {
409 return Some(ExactRational::from_i64(0, 1));
410 }
411 let (m, _n, s, e, _) = x.as_raw_parts()?;
412 let pbuf = m.len() * WORD_BIT_SIZE;
413 let mut num = ExactInt::from_le_words(s, m);
414 let shift = e as i64 - pbuf as i64;
415 if shift >= 0 {
416 num = num.shl(shift as usize);
417 Some(ExactRational::from_ints(num, ExactInt::one()))
418 } else {
419 let den = ExactInt::one().shl((-shift) as usize);
420 Some(ExactRational::from_ints(num, den))
421 }
422}
423
424impl ExactNum {
425 pub fn parse_exact(s: &str) -> Option<Self> {
428 let r = ExactRational::parse_exact(s)?;
429 let den = ExactInt::from_exact_num(r.den())?;
430 if !den.is_one() && !is_pow2(&den) {
431 return None;
432 }
433 let p = den.bit_length().max(WORD_BIT_SIZE);
434 Some(r.to_exact_num(p, RoundingMode::None))
435 }
436
437 pub fn format_exact(&self, rdx: Radix) -> Option<String> {
440 exact_num_to_rational(self)?.format_exact(rdx)
441 }
442}
443
444fn is_pow2(n: &ExactInt) -> bool {
445 if n.is_zero() || n.is_negative() {
446 return false;
447 }
448 n.sub(&ExactInt::one()).bit_length() < n.bit_length()
449}
450
451impl PartialEq for ExactRational {
452 fn eq(&self, other: &Self) -> bool {
453 matches!(self.partial_cmp(other), Some(Ordering::Equal))
454 }
455}
456
457#[cfg(test)]
458mod tests {
459 use super::*;
460 use crate::Consts;
461 use crate::Radix;
462
463 fn r(n: i64, d: i64) -> ExactRational {
464 ExactRational::new(
465 ExactNum::from_i64(n, DEFAULT_P),
466 ExactNum::from_i64(d, DEFAULT_P),
467 )
468 }
469
470 #[test]
471 fn rational_reduce_add_sign_to_float() {
472 let half = r(1, 2);
473 let two_four = r(2, 4);
474 assert_eq!(two_four, half);
475 assert_eq!(ExactRational::from_i64(2, 4), ExactRational::from_i64(1, 2));
476 assert!(!two_four.is_integer());
477 assert_eq!(
478 two_four.num().cmp(&ExactNum::from_u8(1, DEFAULT_P)),
479 Some(0)
480 );
481 assert_eq!(
482 two_four.den().cmp(&ExactNum::from_u8(2, DEFAULT_P)),
483 Some(0)
484 );
485
486 let s = r(1, 3).add(&r(1, 6));
487 assert_eq!(s, half);
488
489 let pos = ExactRational::from_i64(-3, -4);
490 let want = ExactRational::from_i64(3, 4);
491 assert_eq!(pos, want);
492 assert!(pos.den().is_positive());
493
494 let p = 256;
495 let rm = RoundingMode::ToEven;
496 let got = ExactRational::from_i64(1, 3).to_exact_num(p, rm);
497 let want_f = ExactNum::from_i64(1, p).div(&ExactNum::from_i64(3, p), p, rm);
498 assert_eq!(got.cmp(&want_f), Some(0));
499 let mut cc = Consts::new().unwrap();
500 let parsed = ExactNum::parse(
501 "0.33333333333333333333333333333333333333333333333333333333333333333333333333333333",
502 Radix::Dec,
503 p,
504 rm,
505 &mut cc,
506 );
507 assert_eq!(got.cmp(&parsed), Some(0));
508
509 assert!(ExactRational::from_i64(1, 3).partial_cmp(&half) == Some(Ordering::Less));
510 assert!(ExactRational::from_i64(2, 2).is_integer());
511 assert_eq!(
512 ExactRational::from_i64(5, 3).floor(),
513 ExactRational::from_i64(1, 1)
514 );
515 assert_eq!(
516 ExactRational::from_i64(5, 3).ceil(),
517 ExactRational::from_i64(2, 1)
518 );
519 assert!(ExactRational::from_i64(1, 0).is_nan());
520 }
521
522 #[test]
523 fn parse_exact_tenth_half_eighth_sci() {
524 let tenth = ExactRational::parse_exact("0.1").unwrap();
525 assert_eq!(
526 tenth.mul(&ExactRational::from_i64(10, 1)),
527 ExactRational::from_i64(1, 1)
528 );
529
530 let half = ExactNum::parse_exact("0.5").unwrap();
531 let p = half.precision().unwrap();
532 let want = ExactNum::from_u8(1, p).div(&ExactNum::from_u8(2, p), p, RoundingMode::ToEven);
533 assert_eq!(half.cmp(&want), Some(0));
534 assert_eq!(half.ilogb(), Some(-1));
535 let (_m, _n, s, _e, _) = half.as_raw_parts().unwrap();
536 assert_eq!(s, crate::Sign::Pos);
537
538 assert_eq!(
539 ExactRational::parse_exact("0.125")
540 .unwrap()
541 .format_exact(Radix::Dec)
542 .as_deref(),
543 Some("0.125")
544 );
545 assert_eq!(
546 ExactNum::parse_exact("0.125")
547 .unwrap()
548 .format_exact(Radix::Dec)
549 .as_deref(),
550 Some("0.125")
551 );
552 assert!(ExactNum::parse_exact("0.1").is_none());
553
554 let mut cc = Consts::new().unwrap();
555 let sci = ExactNum::parse("1.5e3", Radix::Dec, 256, RoundingMode::ToEven, &mut cc);
556 assert_eq!(sci.cmp(&ExactNum::from_i32(1500, 256)), Some(0));
557 }
558}