continued_fraction.py 9.9 KB

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