Coverage for /usr/lib/python3/dist-packages/sympy/ntheory/continued_fraction.py: 10%
118 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from __future__ import annotations
2from sympy.core.exprtools import factor_terms
3from sympy.core.numbers import Integer, Rational
4from sympy.core.singleton import S
5from sympy.core.symbol import Dummy
6from sympy.core.sympify import _sympify
7from sympy.utilities.misc import as_int
10def continued_fraction(a) -> list:
11 """Return the continued fraction representation of a Rational or
12 quadratic irrational.
14 Examples
15 ========
17 >>> from sympy.ntheory.continued_fraction import continued_fraction
18 >>> from sympy import sqrt
19 >>> continued_fraction((1 + 2*sqrt(3))/5)
20 [0, 1, [8, 3, 34, 3]]
22 See Also
23 ========
24 continued_fraction_periodic, continued_fraction_reduce, continued_fraction_convergents
25 """
26 e = _sympify(a)
27 if all(i.is_Rational for i in e.atoms()):
28 if e.is_Integer:
29 return continued_fraction_periodic(e, 1, 0)
30 elif e.is_Rational:
31 return continued_fraction_periodic(e.p, e.q, 0)
32 elif e.is_Pow and e.exp is S.Half and e.base.is_Integer:
33 return continued_fraction_periodic(0, 1, e.base)
34 elif e.is_Mul and len(e.args) == 2 and (
35 e.args[0].is_Rational and
36 e.args[1].is_Pow and
37 e.args[1].base.is_Integer and
38 e.args[1].exp is S.Half):
39 a, b = e.args
40 return continued_fraction_periodic(0, a.q, b.base, a.p)
41 else:
42 # this should not have to work very hard- no
43 # simplification, cancel, etc... which should be
44 # done by the user. e.g. This is a fancy 1 but
45 # the user should simplify it first:
46 # sqrt(2)*(1 + sqrt(2))/(sqrt(2) + 2)
47 p, d = e.expand().as_numer_denom()
48 if d.is_Integer:
49 if p.is_Rational:
50 return continued_fraction_periodic(p, d)
51 # look for a + b*c
52 # with c = sqrt(s)
53 if p.is_Add and len(p.args) == 2:
54 a, bc = p.args
55 else:
56 a = S.Zero
57 bc = p
58 if a.is_Integer:
59 b = S.NaN
60 if bc.is_Mul and len(bc.args) == 2:
61 b, c = bc.args
62 elif bc.is_Pow:
63 b = Integer(1)
64 c = bc
65 if b.is_Integer and (
66 c.is_Pow and c.exp is S.Half and
67 c.base.is_Integer):
68 # (a + b*sqrt(c))/d
69 c = c.base
70 return continued_fraction_periodic(a, d, c, b)
71 raise ValueError(
72 'expecting a rational or quadratic irrational, not %s' % e)
75def continued_fraction_periodic(p, q, d=0, s=1) -> list:
76 r"""
77 Find the periodic continued fraction expansion of a quadratic irrational.
79 Compute the continued fraction expansion of a rational or a
80 quadratic irrational number, i.e. `\frac{p + s\sqrt{d}}{q}`, where
81 `p`, `q \ne 0` and `d \ge 0` are integers.
83 Returns the continued fraction representation (canonical form) as
84 a list of integers, optionally ending (for quadratic irrationals)
85 with list of integers representing the repeating digits.
87 Parameters
88 ==========
90 p : int
91 the rational part of the number's numerator
92 q : int
93 the denominator of the number
94 d : int, optional
95 the irrational part (discriminator) of the number's numerator
96 s : int, optional
97 the coefficient of the irrational part
99 Examples
100 ========
102 >>> from sympy.ntheory.continued_fraction import continued_fraction_periodic
103 >>> continued_fraction_periodic(3, 2, 7)
104 [2, [1, 4, 1, 1]]
106 Golden ratio has the simplest continued fraction expansion:
108 >>> continued_fraction_periodic(1, 2, 5)
109 [[1]]
111 If the discriminator is zero or a perfect square then the number will be a
112 rational number:
114 >>> continued_fraction_periodic(4, 3, 0)
115 [1, 3]
116 >>> continued_fraction_periodic(4, 3, 49)
117 [3, 1, 2]
119 See Also
120 ========
122 continued_fraction_iterator, continued_fraction_reduce
124 References
125 ==========
127 .. [1] https://en.wikipedia.org/wiki/Periodic_continued_fraction
128 .. [2] K. Rosen. Elementary Number theory and its applications.
129 Addison-Wesley, 3 Sub edition, pages 379-381, January 1992.
131 """
132 from sympy.functions import sqrt, floor
134 p, q, d, s = list(map(as_int, [p, q, d, s]))
136 if d < 0:
137 raise ValueError("expected non-negative for `d` but got %s" % d)
139 if q == 0:
140 raise ValueError("The denominator cannot be 0.")
142 if not s:
143 d = 0
145 # check for rational case
146 sd = sqrt(d)
147 if sd.is_Integer:
148 return list(continued_fraction_iterator(Rational(p + s*sd, q)))
150 # irrational case with sd != Integer
151 if q < 0:
152 p, q, s = -p, -q, -s
154 n = (p + s*sd)/q
155 if n < 0:
156 w = floor(-n)
157 f = -n - w
158 one_f = continued_fraction(1 - f) # 1-f < 1 so cf is [0 ... [...]]
159 one_f[0] -= w + 1
160 return one_f
162 d *= s**2
163 sd *= s
165 if (d - p**2)%q:
166 d *= q**2
167 sd *= q
168 p *= q
169 q *= q
171 terms: list[int] = []
172 pq = {}
174 while (p, q) not in pq:
175 pq[(p, q)] = len(terms)
176 terms.append((p + sd)//q)
177 p = terms[-1]*q - p
178 q = (d - p**2)//q
180 i = pq[(p, q)]
181 return terms[:i] + [terms[i:]] # type: ignore
184def continued_fraction_reduce(cf):
185 """
186 Reduce a continued fraction to a rational or quadratic irrational.
188 Compute the rational or quadratic irrational number from its
189 terminating or periodic continued fraction expansion. The
190 continued fraction expansion (cf) should be supplied as a
191 terminating iterator supplying the terms of the expansion. For
192 terminating continued fractions, this is equivalent to
193 ``list(continued_fraction_convergents(cf))[-1]``, only a little more
194 efficient. If the expansion has a repeating part, a list of the
195 repeating terms should be returned as the last element from the
196 iterator. This is the format returned by
197 continued_fraction_periodic.
199 For quadratic irrationals, returns the largest solution found,
200 which is generally the one sought, if the fraction is in canonical
201 form (all terms positive except possibly the first).
203 Examples
204 ========
206 >>> from sympy.ntheory.continued_fraction import continued_fraction_reduce
207 >>> continued_fraction_reduce([1, 2, 3, 4, 5])
208 225/157
209 >>> continued_fraction_reduce([-2, 1, 9, 7, 1, 2])
210 -256/233
211 >>> continued_fraction_reduce([2, 1, 2, 1, 1, 4, 1, 1, 6, 1, 1, 8]).n(10)
212 2.718281835
213 >>> continued_fraction_reduce([1, 4, 2, [3, 1]])
214 (sqrt(21) + 287)/238
215 >>> continued_fraction_reduce([[1]])
216 (1 + sqrt(5))/2
217 >>> from sympy.ntheory.continued_fraction import continued_fraction_periodic
218 >>> continued_fraction_reduce(continued_fraction_periodic(8, 5, 13))
219 (sqrt(13) + 8)/5
221 See Also
222 ========
224 continued_fraction_periodic
226 """
227 from sympy.solvers import solve
229 period = []
230 x = Dummy('x')
232 def untillist(cf):
233 for nxt in cf:
234 if isinstance(nxt, list):
235 period.extend(nxt)
236 yield x
237 break
238 yield nxt
240 a = S.Zero
241 for a in continued_fraction_convergents(untillist(cf)):
242 pass
244 if period:
245 y = Dummy('y')
246 solns = solve(continued_fraction_reduce(period + [y]) - y, y)
247 solns.sort()
248 pure = solns[-1]
249 rv = a.subs(x, pure).radsimp()
250 else:
251 rv = a
252 if rv.is_Add:
253 rv = factor_terms(rv)
254 if rv.is_Mul and rv.args[0] == -1:
255 rv = rv.func(*rv.args)
256 return rv
259def continued_fraction_iterator(x):
260 """
261 Return continued fraction expansion of x as iterator.
263 Examples
264 ========
266 >>> from sympy import Rational, pi
267 >>> from sympy.ntheory.continued_fraction import continued_fraction_iterator
269 >>> list(continued_fraction_iterator(Rational(3, 8)))
270 [0, 2, 1, 2]
271 >>> list(continued_fraction_iterator(Rational(-3, 8)))
272 [-1, 1, 1, 1, 2]
274 >>> for i, v in enumerate(continued_fraction_iterator(pi)):
275 ... if i > 7:
276 ... break
277 ... print(v)
278 3
279 7
280 15
281 1
282 292
283 1
284 1
285 1
287 References
288 ==========
290 .. [1] https://en.wikipedia.org/wiki/Continued_fraction
292 """
293 from sympy.functions import floor
294 while True:
295 i = floor(x)
296 yield i
297 x -= i
298 if not x:
299 break
300 x = 1/x
303def continued_fraction_convergents(cf):
304 """
305 Return an iterator over the convergents of a continued fraction (cf).
307 The parameter should be an iterable returning successive
308 partial quotients of the continued fraction, such as might be
309 returned by continued_fraction_iterator. In computing the
310 convergents, the continued fraction need not be strictly in
311 canonical form (all integers, all but the first positive).
312 Rational and negative elements may be present in the expansion.
314 Examples
315 ========
317 >>> from sympy.core import pi
318 >>> from sympy import S
319 >>> from sympy.ntheory.continued_fraction import \
320 continued_fraction_convergents, continued_fraction_iterator
322 >>> list(continued_fraction_convergents([0, 2, 1, 2]))
323 [0, 1/2, 1/3, 3/8]
325 >>> list(continued_fraction_convergents([1, S('1/2'), -7, S('1/4')]))
326 [1, 3, 19/5, 7]
328 >>> it = continued_fraction_convergents(continued_fraction_iterator(pi))
329 >>> for n in range(7):
330 ... print(next(it))
331 3
332 22/7
333 333/106
334 355/113
335 103993/33102
336 104348/33215
337 208341/66317
339 See Also
340 ========
342 continued_fraction_iterator
344 """
345 p_2, q_2 = S.Zero, S.One
346 p_1, q_1 = S.One, S.Zero
347 for a in cf:
348 p, q = a*p_1 + p_2, a*q_1 + q_2
349 p_2, q_2 = p_1, q_1
350 p_1, q_1 = p, q
351 yield p/q