use crate::ops::rbig_from_signed;
use crate::{IBig, RBig, UBig};
use oxinum_core::{OxiNumError, OxiNumResult};
pub fn stern_brocot_path(x: &RBig) -> OxiNumResult<Vec<bool>> {
if x.numerator() <= &IBig::ZERO {
return Err(OxiNumError::Parse(
"Stern-Brocot tree only enumerates positive rationals".into(),
));
}
let mut left_n = IBig::ZERO;
let mut left_d = IBig::ONE;
let mut right_n = IBig::ONE;
let mut right_d = IBig::ZERO;
let target_n = x.numerator().clone();
let target_d: IBig = x.denominator().clone().into();
let mut path = Vec::new();
loop {
let mid_n = &left_n + &right_n;
let mid_d = &left_d + &right_d;
let lhs = &target_n * &mid_d;
let rhs = &mid_n * &target_d;
match lhs.cmp(&rhs) {
core::cmp::Ordering::Equal => break,
core::cmp::Ordering::Less => {
path.push(false);
right_n = mid_n;
right_d = mid_d;
}
core::cmp::Ordering::Greater => {
path.push(true);
left_n = mid_n;
left_d = mid_d;
}
}
}
Ok(path)
}
pub fn from_stern_brocot_path(path: &[bool]) -> RBig {
let mut left_n = IBig::ZERO;
let mut left_d = IBig::ONE;
let mut right_n = IBig::ONE;
let mut right_d = IBig::ZERO;
for &turn in path {
let mid_n = &left_n + &right_n;
let mid_d = &left_d + &right_d;
if turn {
left_n = mid_n;
left_d = mid_d;
} else {
right_n = mid_n;
right_d = mid_d;
}
}
let final_n = &left_n + &right_n;
let final_d = &left_d + &right_d;
rbig_from_signed(&final_n, &final_d)
}
pub fn farey_sequence(n: u64) -> Vec<RBig> {
let mut out = Vec::new();
if n == 0 {
return out;
}
let n_i = n as i128;
let (mut a, mut b) = (0_i128, 1_i128);
let (mut c, mut d) = (1_i128, n_i);
out.push(rbig_from_pair(a, b)); loop {
out.push(rbig_from_pair(c, d));
if c == 1 && d == 1 {
break;
}
let k = (n_i + b) / d;
let next_p = k * c - a;
let next_q = k * d - b;
a = c;
b = d;
c = next_p;
d = next_q;
}
out
}
fn rbig_from_pair(num: i128, den: i128) -> RBig {
let num_b = IBig::from(num);
let den_b = if den >= 0 {
UBig::from(den as u128)
} else {
UBig::from(den.unsigned_abs())
};
RBig::from_parts(num_b, den_b)
}
#[cfg(test)]
mod tests {
use super::*;
fn r(n: i32, d: u32) -> RBig {
RBig::from_parts(IBig::from(n), UBig::from(d))
}
#[test]
fn sb_path_one_is_empty() {
let path = stern_brocot_path(&RBig::ONE).expect("ok");
assert!(path.is_empty());
}
#[test]
fn sb_path_one_half() {
let path = stern_brocot_path(&r(1, 2)).expect("ok");
assert_eq!(path, vec![false]);
}
#[test]
fn sb_path_two_over_one() {
let path = stern_brocot_path(&r(2, 1)).expect("ok");
assert_eq!(path, vec![true]);
}
#[test]
fn sb_path_zero_errors() {
assert!(stern_brocot_path(&RBig::ZERO).is_err());
}
#[test]
fn sb_path_negative_errors() {
let neg = RBig::from_parts(IBig::from(-3), UBig::from(4u32));
assert!(stern_brocot_path(&neg).is_err());
}
#[test]
fn sb_from_empty_is_one() {
assert_eq!(from_stern_brocot_path(&[]), RBig::ONE);
}
#[test]
fn sb_from_single_l() {
assert_eq!(from_stern_brocot_path(&[false]), r(1, 2));
}
#[test]
fn sb_from_single_r() {
assert_eq!(from_stern_brocot_path(&[true]), r(2, 1));
}
#[test]
fn sb_roundtrip_22_over_7() {
let r = r(22, 7);
let path = stern_brocot_path(&r).expect("ok");
let back = from_stern_brocot_path(&path);
assert_eq!(back, r);
}
#[test]
fn sb_roundtrip_355_over_113() {
let r = r(355, 113);
let path = stern_brocot_path(&r).expect("ok");
let back = from_stern_brocot_path(&path);
assert_eq!(back, r);
}
#[test]
fn sb_roundtrip_various() {
let cases = [
(1, 1u32),
(1, 2),
(2, 1),
(3, 7),
(22, 7),
(355, 113),
(17, 12),
(100, 3),
(5, 8),
];
for (n, d) in cases {
let original = r(n, d);
let path = stern_brocot_path(&original).expect("ok");
let back = from_stern_brocot_path(&path);
assert_eq!(back, original, "roundtrip failed for {n}/{d}");
}
}
#[test]
fn farey_zero() {
assert!(farey_sequence(0).is_empty());
}
#[test]
fn farey_one() {
let f = farey_sequence(1);
assert_eq!(f, vec![RBig::ZERO, RBig::ONE]);
}
#[test]
fn farey_five_exact() {
let f5 = farey_sequence(5);
let expected = vec![
r(0, 1),
r(1, 5),
r(1, 4),
r(1, 3),
r(2, 5),
r(1, 2),
r(3, 5),
r(2, 3),
r(3, 4),
r(4, 5),
r(1, 1),
];
assert_eq!(f5, expected);
}
#[test]
fn farey_five_strictly_ascending() {
let f5 = farey_sequence(5);
for w in f5.windows(2) {
assert!(w[0] < w[1], "Farey sequence not strictly ascending");
}
}
#[test]
fn farey_endpoints() {
for n in 1..=8 {
let f = farey_sequence(n);
assert_eq!(f.first(), Some(&RBig::ZERO));
assert_eq!(f.last(), Some(&RBig::ONE));
}
}
#[test]
fn farey_ten_length() {
let f = farey_sequence(10);
assert_eq!(f.len(), 33);
for w in f.windows(2) {
assert!(w[0] < w[1]);
}
}
#[test]
fn farey_terms_reduced() {
let f = farey_sequence(7);
for x in &f {
let num = x.numerator().clone();
let den: IBig = x.denominator().clone().into();
let abs_num = if num < IBig::ZERO {
-num.clone()
} else {
num.clone()
};
let canonical = rbig_from_signed(&abs_num, &den);
assert_eq!(canonical.numerator(), &abs_num);
}
}
}