Skip to main content

cas_domain/
rational.rs

1//! 任意精度有理数。
2//!
3//! 规范形:分母为正、分子分母既约、零表示为 `0/1`。
4//! `Small{ n: i64, d: u64 }` 是常用路径;分子越界(分母恒可放 u64)时落 `Big`。
5//! 既约性使相等与哈希和表示无关;`d = 1` 合法(expr 层在节点上把它归一为 Int)。
6
7use super::integer::{Integer, big_gcd, big_sign, gcd_u64};
8use num_bigint::BigInt;
9use std::cmp::Ordering;
10use std::fmt;
11use std::hash::{Hash, Hasher};
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14enum Repr {
15    Small { n: i64, d: u64 },
16    Big(Box<(BigInt, BigInt)>),
17}
18
19/// 任意精度有理数,规范形见模块文档。
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct Rational(Repr);
22
23impl Rational {
24    pub fn zero() -> Self {
25        Rational(Repr::Small { n: 0, d: 1 })
26    }
27
28    pub fn one() -> Self {
29        Rational(Repr::Small { n: 1, d: 1 })
30    }
31
32    pub fn from_integer(v: &Integer) -> Self {
33        match v.to_i64() {
34            Some(n) => Rational(Repr::Small { n, d: 1 }),
35            None => Rational(Repr::Big(Box::new((v.to_bigint(), BigInt::from(1))))),
36        }
37    }
38
39    /// 由整数分子分母构造并规范化;`d = 0` 返回 `None`。
40    pub fn from_ints(n: &Integer, d: &Integer) -> Option<Self> {
41        if d.is_zero() {
42            return None;
43        }
44        // 符号挪到分子,分母取正
45        let (n, d) = if d.is_negative() {
46            (n.neg(), d.abs())
47        } else {
48            (n.clone(), d.clone())
49        };
50        if n.is_zero() {
51            return Some(Rational::zero());
52        }
53        let normalized = match (n.to_i64(), d.to_i64()) {
54            (Some(sn), Some(sd)) => {
55                let sd = sd as u64;
56                let g = gcd_u64(sn.unsigned_abs(), sd);
57                let nn = (sn as i128 / g as i128) as i64;
58                Rational(Repr::Small { n: nn, d: sd / g })
59            }
60            _ => Self::from_big_parts(n.to_bigint(), d.to_bigint()),
61        };
62        Some(normalized)
63    }
64
65    pub fn is_zero(&self) -> bool {
66        matches!(self.0, Repr::Small { n: 0, .. })
67    }
68
69    pub fn is_one(&self) -> bool {
70        matches!(self.0, Repr::Small { n: 1, d: 1 })
71    }
72
73    pub fn is_negative(&self) -> bool {
74        self.sign() < 0
75    }
76
77    pub fn sign(&self) -> i32 {
78        match &self.0 {
79            Repr::Small { n, .. } => n.signum() as i32,
80            Repr::Big(p) => match big_sign(&p.0) {
81                Ordering::Less => -1,
82                Ordering::Equal => 0,
83                Ordering::Greater => 1,
84            },
85        }
86    }
87
88    pub fn num(&self) -> Integer {
89        match &self.0 {
90            Repr::Small { n, .. } => Integer::from_i64(*n),
91            Repr::Big(p) => Integer::from_bigint(p.0.clone()),
92        }
93    }
94
95    pub fn den(&self) -> Integer {
96        match &self.0 {
97            // Small 表示的分母是 u64,可超过 i64::MAX——必须经 from_u64 落位
98            Repr::Small { d, .. } => Integer::from_u64(*d),
99            Repr::Big(p) => Integer::from_bigint(p.1.clone()),
100        }
101    }
102
103    pub fn neg(&self) -> Self {
104        match &self.0 {
105            Repr::Small { n, d } => Rational(Repr::Small { n: -n, d: *d }),
106            Repr::Big(p) => Self::from_big_parts(-p.0.clone(), p.1.clone()),
107        }
108    }
109
110    pub fn abs(&self) -> Self {
111        if self.is_negative() {
112            self.neg()
113        } else {
114            self.clone()
115        }
116    }
117
118    /// 乘法逆元;零无逆元。
119    pub fn inv(&self) -> Option<Self> {
120        Self::from_ints(&self.den(), &self.num())
121    }
122
123    pub fn add(&self, other: &Self) -> Self {
124        match (&self.0, &other.0) {
125            (Repr::Small { n: n1, d: d1 }, Repr::Small { n: n2, d: d2 }) => {
126                let n = (*n1 as i128) * (*d2 as i128) + (*n2 as i128) * (*d1 as i128);
127                let d = (*d1 as u128) * (*d2 as u128);
128                Self::from_parts_wide(n, d)
129            }
130            _ => Self::from_big_parts(
131                self.n_big() * other.d_big() + other.n_big() * self.d_big(),
132                self.d_big() * other.d_big(),
133            ),
134        }
135    }
136
137    pub fn sub(&self, other: &Self) -> Self {
138        self.add(&other.neg())
139    }
140
141    pub fn mul(&self, other: &Self) -> Self {
142        match (&self.0, &other.0) {
143            (Repr::Small { n: n1, d: d1 }, Repr::Small { n: n2, d: d2 }) => {
144                let n = (*n1 as i128) * (*n2 as i128);
145                let d = (*d1 as u128) * (*d2 as u128);
146                Self::from_parts_wide(n, d)
147            }
148            _ => Self::from_big_parts(self.n_big() * other.n_big(), self.d_big() * other.d_big()),
149        }
150    }
151
152    /// 有理数相除;除零返回 `None`。
153    pub fn div(&self, other: &Self) -> Option<Self> {
154        Some(self.mul(&other.inv()?))
155    }
156
157    /// 非负整数幂。规模守卫由调用方(expr 层)负责。
158    pub fn pow(&self, exp: u32) -> Self {
159        Self::from_ints(&self.num().pow(exp), &self.den().pow(exp)).expect("分母非零")
160    }
161
162    /// 非负整数幂(快速路径):既约有理数的幂仍既约——`gcd(num,den)^m = 1`,
163    /// 无需再跑 gcd。大幂折叠产生几万比特常数时 gcd 是平方级瓶颈
164    /// (perf_probe case 1431 实测),此路径跳过它。
165    ///
166    /// 落位判定必须与 from_big_parts 一致:分母按 u64 判(不是 i64),
167    /// 否则同值出现两种表示,破坏"规范形与表示无关"(29^13 案例实测)。
168    pub fn pow_reduced(&self, exp: u32) -> Self {
169        let n = self.num().pow(exp);
170        let d = self.den().pow(exp);
171        match (n.to_i64(), d.to_u64()) {
172            (Some(sn), Some(sd)) => Rational(Repr::Small { n: sn, d: sd }),
173            _ => Rational(Repr::Big(Box::new((n.to_bigint(), d.to_bigint())))),
174        }
175    }
176
177    /// f64 近似(数值交叉验证路径;大数经十进制字符串中转,精度为 f64 语义)。
178    pub fn to_f64(&self) -> f64 {
179        match &self.0 {
180            Repr::Small { n, d } => (*n as f64) / (*d as f64),
181            Repr::Big(p) => {
182                let n: f64 = p.0.to_string().parse().unwrap_or(f64::INFINITY);
183                let d: f64 = p.1.to_string().parse().unwrap_or(f64::INFINITY);
184                n / d
185            }
186        }
187    }
188
189    /// 乘法逆元(快速路径,前提:自身既约——Rational 规范形保证);零无逆元。
190    /// 逆元的既约性继承自原数,跳过 gcd。
191    pub fn inv_reduced(&self) -> Option<Self> {
192        match &self.0 {
193            Repr::Small { n: 0, .. } => None,
194            Repr::Small { n, d } => {
195                // 逆元 = d/n(符号归分子);Small 的 d 是 u64,可超 i64::MAX
196                if *d <= i64::MAX as u64 {
197                    let num = *d as i64;
198                    Some(Rational(Repr::Small {
199                        n: if *n < 0 { -num } else { num },
200                        d: n.unsigned_abs(),
201                    }))
202                } else {
203                    let num = Integer::from_u64(*d);
204                    let num = if *n < 0 { num.neg() } else { num };
205                    Some(Rational(Repr::Big(Box::new((
206                        num.to_bigint(),
207                        BigInt::from(n.unsigned_abs()),
208                    )))))
209                }
210            }
211            Repr::Big(p) => {
212                let (num, den) = if p.0.sign() == num_bigint::Sign::Minus {
213                    (-p.1.clone(), -p.0.clone())
214                } else {
215                    (p.1.clone(), p.0.clone())
216                };
217                Some(Rational(Repr::Big(Box::new((num, den)))))
218            }
219        }
220    }
221
222    /// 与整数比较:`self - o` 的符号。
223    pub fn cmp_integer(&self, o: &Integer) -> Ordering {
224        match (&self.0, o.to_i64()) {
225            (Repr::Small { n, d }, Some(i)) => (*n as i128).cmp(&((i as i128) * (*d as i128))),
226            _ => self.n_big().cmp(&(o.to_bigint() * self.d_big())),
227        }
228    }
229
230    fn from_parts_wide(n: i128, d: u128) -> Self {
231        debug_assert!(d > 0);
232        if n == 0 {
233            return Rational::zero();
234        }
235        let g = gcd_u128(n.unsigned_abs(), d);
236        let (n, d) = (n / g as i128, d / g);
237        match (i64::try_from(n), u64::try_from(d)) {
238            (Ok(sn), Ok(sd)) => Rational(Repr::Small { n: sn, d: sd }),
239            _ => Self::from_big_parts(BigInt::from(n), BigInt::from(d)),
240        }
241    }
242
243    fn from_big_parts(n: BigInt, d: BigInt) -> Self {
244        debug_assert!(d.sign() != num_bigint::Sign::Minus);
245        let g = big_gcd(&n, &d);
246        let (n, d) = if g == BigInt::from(1) {
247            (n, d)
248        } else {
249            (n / &g, d / &g)
250        };
251        // 归约后能放回 Small 的必须放回:规范形与表示无关的前提
252        match (i64::try_from(&n), u64::try_from(&d)) {
253            (Ok(sn), Ok(sd)) => Rational(Repr::Small { n: sn, d: sd }),
254            _ => Rational(Repr::Big(Box::new((n, d)))),
255        }
256    }
257
258    fn n_big(&self) -> BigInt {
259        match &self.0 {
260            Repr::Small { n, .. } => BigInt::from(*n),
261            Repr::Big(p) => p.0.clone(),
262        }
263    }
264
265    fn d_big(&self) -> BigInt {
266        match &self.0 {
267            Repr::Small { d, .. } => BigInt::from(*d),
268            Repr::Big(p) => p.1.clone(),
269        }
270    }
271}
272
273fn gcd_u128(mut a: u128, mut b: u128) -> u128 {
274    while b != 0 {
275        let r = a % b;
276        a = b;
277        b = r;
278    }
279    a
280}
281
282impl Ord for Rational {
283    fn cmp(&self, other: &Self) -> Ordering {
284        match (&self.0, &other.0) {
285            (Repr::Small { n: n1, d: d1 }, Repr::Small { n: n2, d: d2 }) => {
286                let l = (*n1 as i128) * (*d2 as i128);
287                let r = (*n2 as i128) * (*d1 as i128);
288                l.cmp(&r)
289            }
290            _ => (self.n_big() * other.d_big()).cmp(&(other.n_big() * self.d_big())),
291        }
292    }
293}
294
295impl PartialOrd for Rational {
296    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
297        Some(self.cmp(other))
298    }
299}
300
301impl Hash for Rational {
302    fn hash<H: Hasher>(&self, state: &mut H) {
303        match &self.0 {
304            Repr::Small { n, d } => {
305                n.hash(state);
306                d.hash(state);
307            }
308            Repr::Big(p) => {
309                p.0.hash(state);
310                p.1.hash(state);
311            }
312        }
313    }
314}
315
316impl fmt::Display for Rational {
317    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318        match &self.0 {
319            Repr::Small { n, d: 1 } => write!(f, "{n}"),
320            Repr::Small { n, d } => write!(f, "{n}/{d}"),
321            Repr::Big(p) if p.1 == BigInt::from(1) => write!(f, "{}", p.0),
322            Repr::Big(p) => write!(f, "{}/{}", p.0, p.1),
323        }
324    }
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330
331    fn r(n: i64, d: i64) -> Rational {
332        Rational::from_ints(&Integer::from_i64(n), &Integer::from_i64(d)).unwrap()
333    }
334
335    #[test]
336    fn 规范化() {
337        assert_eq!(r(4, 8), r(1, 2));
338        assert_eq!(r(-6, 4), r(-3, 2));
339        assert_eq!(r(6, -4), r(-3, 2));
340        assert_eq!(r(0, 7), Rational::zero());
341        assert_eq!(r(9, 3).to_string(), "3");
342        assert_eq!(r(7, 3).to_string(), "7/3");
343        assert_eq!(
344            Rational::from_ints(&Integer::from_i64(1), &Integer::zero()),
345            None
346        );
347    }
348
349    #[test]
350    fn 四则() {
351        assert_eq!(r(1, 2).add(&r(1, 3)), r(5, 6));
352        assert_eq!(r(1, 2).sub(&r(1, 3)), r(1, 6));
353        assert_eq!(r(2, 3).mul(&r(3, 4)), r(1, 2));
354        assert_eq!(r(1, 2).div(&r(3, 4)), Some(r(2, 3)));
355        assert_eq!(r(1, 2).div(&Rational::zero()), None);
356        // 1/3 * 3 归一到整数分母
357        assert_eq!(r(1, 3).mul(&r(3, 1)), Rational::one());
358    }
359
360    #[test]
361    fn 大数路径() {
362        let big = Integer::parse("9223372036854775808").unwrap(); // 2^63
363        let q = Rational::from_ints(&big, &Integer::from_i64(2)).unwrap();
364        assert_eq!(q.to_string(), "4611686018427387904");
365        // 分子越界时 Big 表示与 Small 表示的序一致
366        assert!(q.cmp(&r(1, 1)) == Ordering::Greater);
367        // 2^62 × 1/2 = 2^61,跨表示乘法保持精确
368        assert_eq!(q.mul(&r(1, 2)).to_string(), "2305843009213693952");
369        // i64::MIN 作分子
370        let m = Integer::from_i64(i64::MIN);
371        let qm = Rational::from_ints(&m, &Integer::from_i64(2)).unwrap();
372        assert_eq!(qm.to_string(), "-4611686018427387904");
373    }
374
375    #[test]
376    fn 幂与逆元() {
377        assert_eq!(r(2, 3).pow(3), r(8, 27));
378        assert_eq!(r(3, 2).inv(), Some(r(2, 3)));
379        assert_eq!(r(-3, 2).inv(), Some(r(-2, 3)));
380        assert_eq!(Rational::zero().inv(), None);
381    }
382
383    #[test]
384    fn 与整数比较() {
385        assert_eq!(
386            r(5, 2).cmp_integer(&Integer::from_i64(2)),
387            Ordering::Greater
388        );
389        assert_eq!(r(5, 2).cmp_integer(&Integer::from_i64(3)), Ordering::Less);
390        assert_eq!(r(4, 2).cmp_integer(&Integer::from_i64(2)), Ordering::Equal);
391        let big = Integer::parse("99999999999999999999").unwrap();
392        assert_eq!(r(1, 1).cmp_integer(&big), Ordering::Less);
393    }
394
395    #[test]
396    fn 快速路径落位一致性() {
397        // 29^13 超过 i64::MAX 但在 u64 内:pow_reduced/inv_reduced 的落位判定
398        // 必须与 from_ints/from_big_parts 一致,否则同值出现两种表示,
399        // 破坏"规范形与表示无关"(proptide 回归案例:(-29/2)^-13 往返失败)。
400        let d13 = Integer::parse("10260628712958602189").unwrap();
401        let r = Rational::from_ints(&Integer::from_i64(-2), &Integer::from_i64(29)).unwrap();
402        let p = r.pow_reduced(13);
403        let expect = Rational::from_ints(&Integer::from_i64(-8192), &d13).unwrap();
404        assert_eq!(p, expect); // PartialEq 比较表示:此处即断言同表示
405        assert_eq!(p.to_string(), "-8192/10260628712958602189");
406
407        // 分母超 i64 的逆元:分子落 Big,与通用路径同表示
408        let q = Rational::from_ints(&Integer::from_i64(3), &d13).unwrap();
409        let inv = q.inv_reduced().unwrap();
410        let expect_inv = Rational::from_ints(&d13, &Integer::from_i64(3)).unwrap();
411        assert_eq!(inv, expect_inv);
412    }
413}