proof_engine/number_theory/
continued_fractions.rs1use glam::{Vec2, Vec3, Vec4};
4
5#[derive(Debug, Clone, PartialEq)]
7pub struct ContinuedFraction {
8 pub integer_part: i64,
9 pub coefficients: Vec<u64>,
10}
11
12impl ContinuedFraction {
13 pub fn new(integer_part: i64, coefficients: Vec<u64>) -> Self {
15 Self { integer_part, coefficients }
16 }
17
18 pub fn from_rational(mut num: i64, mut den: i64) -> Self {
20 if den == 0 {
21 return Self { integer_part: 0, coefficients: vec![] };
22 }
23 if den < 0 {
24 num = -num;
25 den = -den;
26 }
27
28 let integer_part = num.div_euclid(den);
30 let mut remainder = num.rem_euclid(den);
31 let mut coefficients = Vec::new();
32 let mut d = den;
33
34 while remainder != 0 {
35 let new_d = remainder;
36 let q = d / remainder;
37 remainder = d % remainder;
38 d = new_d;
39 coefficients.push(q as u64);
40 }
41
42 Self { integer_part, coefficients }
43 }
44
45 pub fn from_f64(x: f64, max_terms: usize) -> Self {
47 let integer_part = x.floor() as i64;
48 let mut frac = x - integer_part as f64;
49 let mut coefficients = Vec::new();
50
51 for _ in 0..max_terms {
52 if frac.abs() < 1e-12 {
53 break;
54 }
55 let recip = 1.0 / frac;
56 let a = recip.floor() as u64;
57 if a == 0 {
58 break;
59 }
60 coefficients.push(a);
61 frac = recip - a as f64;
62 }
63
64 Self { integer_part, coefficients }
65 }
66
67 pub fn convergents(&self) -> Vec<(i64, i64)> {
70 let mut result = Vec::new();
71 let mut h_prev: i64 = 1;
72 let mut k_prev: i64 = 0;
73 let mut h_curr: i64 = self.integer_part;
74 let mut k_curr: i64 = 1;
75 result.push((h_curr, k_curr));
76
77 for &a in &self.coefficients {
78 let a = a as i64;
79 let h_next = a * h_curr + h_prev;
80 let k_next = a * k_curr + k_prev;
81 h_prev = h_curr;
82 k_prev = k_curr;
83 h_curr = h_next;
84 k_curr = k_next;
85 result.push((h_curr, k_curr));
86 }
87
88 result
89 }
90
91 pub fn to_f64(&self) -> f64 {
93 let mut value = 0.0f64;
94 for &a in self.coefficients.iter().rev() {
95 value = 1.0 / (a as f64 + value);
96 }
97 self.integer_part as f64 + value
98 }
99
100 pub fn len(&self) -> usize {
102 self.coefficients.len()
103 }
104
105 pub fn is_empty(&self) -> bool {
106 self.coefficients.is_empty()
107 }
108}
109
110pub fn sqrt_cf(n: u64) -> ContinuedFraction {
113 let sqrt_n = (n as f64).sqrt();
114 let a0 = sqrt_n.floor() as u64;
115 if a0 * a0 == n {
116 return ContinuedFraction {
118 integer_part: a0 as i64,
119 coefficients: vec![],
120 };
121 }
122
123 let mut coefficients = Vec::new();
124 let mut m: i64 = 0;
125 let mut d: i64 = 1;
126 let mut a = a0 as i64;
127
128 loop {
129 m = d * a - m;
130 d = (n as i64 - m * m) / d;
131 if d == 0 {
132 break;
133 }
134 a = (a0 as i64 + m) / d;
135 coefficients.push(a as u64);
136 if a == 2 * a0 as i64 {
138 break;
139 }
140 }
141
142 ContinuedFraction {
143 integer_part: a0 as i64,
144 coefficients,
145 }
146}
147
148pub struct ConvergentVisualizer {
150 pub origin: Vec3,
151 pub scale: f32,
152 pub target: f64,
153}
154
155pub struct ConvergentGlyph {
156 pub index: usize,
157 pub value: f64,
158 pub numerator: i64,
159 pub denominator: i64,
160 pub position: Vec3,
161 pub color: Vec4,
162 pub character: char,
163}
164
165impl ConvergentVisualizer {
166 pub fn new(origin: Vec3, scale: f32, target: f64) -> Self {
167 Self { origin, scale, target }
168 }
169
170 pub fn render(&self, cf: &ContinuedFraction) -> Vec<ConvergentGlyph> {
172 let convergents = cf.convergents();
173 convergents
174 .iter()
175 .enumerate()
176 .map(|(i, &(h, k))| {
177 let value = h as f64 / k as f64;
178 let error = (value - self.target).abs();
179 let x_offset = (value - self.target) as f32 * self.scale;
180 let y_offset = i as f32 * 0.5; let err_color = (1.0 - (error as f32 * 10.0).min(1.0)).max(0.0);
182 ConvergentGlyph {
183 index: i,
184 value,
185 numerator: h,
186 denominator: k,
187 position: self.origin + Vec3::new(x_offset, y_offset, 0.0),
188 color: Vec4::new(1.0 - err_color, err_color, 0.3, 1.0),
189 character: if i % 2 == 0 { '+' } else { '-' },
190 }
191 })
192 .collect()
193 }
194}
195
196#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn rational_cf() {
204 let cf = ContinuedFraction::from_rational(355, 113);
208 assert_eq!(cf.integer_part, 3);
209 assert_eq!(cf.coefficients, vec![7, 16]);
210 }
211
212 #[test]
213 fn rational_roundtrip() {
214 let cf = ContinuedFraction::from_rational(22, 7);
215 assert_eq!(cf.integer_part, 3);
216 assert_eq!(cf.coefficients, vec![7]);
217 let val = cf.to_f64();
218 assert!((val - 22.0 / 7.0).abs() < 1e-12);
219 }
220
221 #[test]
222 fn pi_cf() {
223 let cf = ContinuedFraction::from_f64(std::f64::consts::PI, 10);
224 assert_eq!(cf.integer_part, 3);
225 assert_eq!(cf.coefficients[0], 7);
227 assert_eq!(cf.coefficients[1], 15);
228 assert_eq!(cf.coefficients[2], 1);
229 assert_eq!(cf.coefficients[3], 292);
230 }
231
232 #[test]
233 fn convergents_of_pi() {
234 let cf = ContinuedFraction::from_f64(std::f64::consts::PI, 5);
235 let conv = cf.convergents();
236 assert_eq!(conv[0], (3, 1));
238 assert_eq!(conv[1], (22, 7));
240 assert_eq!(conv[2], (333, 106));
242 assert_eq!(conv[3], (355, 113));
244 }
245
246 #[test]
247 fn to_f64_accuracy() {
248 let cf = ContinuedFraction::from_f64(std::f64::consts::E, 15);
249 let val = cf.to_f64();
250 assert!((val - std::f64::consts::E).abs() < 1e-10);
251 }
252
253 #[test]
254 fn sqrt_2_cf() {
255 let cf = sqrt_cf(2);
256 assert_eq!(cf.integer_part, 1);
257 assert_eq!(cf.coefficients[0], 2);
259 assert_eq!(*cf.coefficients.last().unwrap(), 2);
261 }
262
263 #[test]
264 fn sqrt_3_cf() {
265 let cf = sqrt_cf(3);
266 assert_eq!(cf.integer_part, 1);
267 assert_eq!(cf.coefficients, vec![1, 2]);
269 }
270
271 #[test]
272 fn sqrt_7_cf() {
273 let cf = sqrt_cf(7);
274 assert_eq!(cf.integer_part, 2);
275 assert_eq!(cf.coefficients, vec![1, 1, 1, 4]);
277 }
278
279 #[test]
280 fn perfect_square_cf() {
281 let cf = sqrt_cf(9);
282 assert_eq!(cf.integer_part, 3);
283 assert!(cf.coefficients.is_empty());
284 }
285
286 #[test]
287 fn convergent_visualizer() {
288 let cf = ContinuedFraction::from_f64(std::f64::consts::PI, 5);
289 let vis = ConvergentVisualizer::new(Vec3::ZERO, 100.0, std::f64::consts::PI);
290 let glyphs = vis.render(&cf);
291 assert!(!glyphs.is_empty());
292 let last = glyphs.last().unwrap();
294 assert!((last.value - std::f64::consts::PI).abs() < 1e-6);
295 }
296
297 #[test]
298 fn negative_rational() {
299 let cf = ContinuedFraction::from_rational(-7, 3);
300 assert_eq!(cf.integer_part, -3);
304 let val = cf.to_f64();
305 assert!((val - (-7.0 / 3.0)).abs() < 1e-12);
306 }
307
308 #[test]
309 fn from_rational_zero() {
310 let cf = ContinuedFraction::from_rational(0, 1);
311 assert_eq!(cf.integer_part, 0);
312 assert!(cf.coefficients.is_empty());
313 }
314}