;;;
;;; rational.lsp : 有理数 (ISLisp 用)
;;;
;;; Copyright (c) 2024 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
;;; メソッドの宣言
(defgeneric +/ (x y))
(defgeneric -/ (x y))
(defgeneric */ (x y))
(defgeneric // (x y))
(defgeneric inv (x))
(defgeneric =/ (x y))
(defgeneric /=/ (x y))
(defgeneric </ (x y))
(defgeneric <=/ (x y))
(defgeneric >/ (x y))
(defgeneric >=/ (x y))
(defgeneric is-integer (x))
(defgeneric to-integer (x))
(defgeneric to-float (x))
(defgeneric to-string (x))
(defgeneric display/ (x))
(defgeneric repeat-decimal (x))
(defgeneric from-repeat-decimal (x y))
;;; 有理数
(defclass <rational> ()
((num :accessor numerator :initform 0 :initarg num)
(den :accessor denominator :initform 1 :initarg den)))
;;; コンストラクタ
(defun make-ratio (a b)
(if (= b 0)
(error "divizion by zero"))
(let ((f (if (< b 0) -1 1))
(z (gcd (abs a) (abs b))))
(create (class <rational>) 'num (div (* f a) z) 'den (div (* f b) z))))
;;; 型述語
(defun rationalp (x)
(eq (class-of x) (class <rational>)))
;;; 算術演算子
(defmethod +/ ((x <rational>) (y <rational>))
(let ((x1 (numerator x)) (x2 (denominator x))
(y1 (numerator y)) (y2 (denominator y)))
(make-ratio (+ (* x1 y2) (* x2 y1)) (* x2 y2))))
(defmethod +/ ((x <rational>) (y <integer>))
(let ((x1 (numerator x)) (x2 (denominator x)))
(make-ratio (+ x1 (* x2 y)) x2)))
(defmethod +/ ((x <integer>) (y <rational>))
(let ((y1 (numerator y)) (y2 (denominator y)))
(make-ratio (+ (* x y2) y1) y2)))
(defmethod +/ ((x <integer>) (y <integer>))
(make-ratio (+ x y) 1))
(defmethod -/ ((x <rational>) (y <rational>))
(let ((x1 (numerator x)) (x2 (denominator x))
(y1 (numerator y)) (y2 (denominator y)))
(make-ratio (- (* x1 y2) (* x2 y1)) (* x2 y2))))
(defmethod -/ ((x <rational>) (y <integer>))
(let ((x1 (numerator x)) (x2 (denominator x)))
(make-ratio (- x1 (* x2 y)) x2)))
(defmethod -/ ((x <integer>) (y <rational>))
(let ((y1 (numerator y)) (y2 (denominator y)))
(make-ratio (- (* x y2) y1) y2)))
(defmethod -/ ((x <integer>) (y <integer>))
(make-ratio (- x y) 1))
(defmethod */ ((x <rational>) (y <rational>))
(let ((x1 (numerator x)) (x2 (denominator x))
(y1 (numerator y)) (y2 (denominator y)))
(make-ratio (* x1 y1) (* x2 y2))))
(defmethod */ ((x <rational>) (y <integer>))
(let ((x1 (numerator x)) (x2 (denominator x)))
(make-ratio (* x1 y) x2)))
(defmethod */ ((x <integer>) (y <rational>))
(let ((y1 (numerator y)) (y2 (denominator y)))
(make-ratio (* x y1) y2)))
(defmethod */ ((x <integer>) (y <integer>))
(make-ratio (* x y) 1))
(defmethod // ((x <rational>) (y <rational>))
(let ((x1 (numerator x)) (x2 (denominator x))
(y1 (numerator y)) (y2 (denominator y)))
(make-ratio (* x1 y2) (* x2 y1))))
(defmethod // ((x <rational>) (y <integer>))
(let ((x1 (numerator x)) (x2 (denominator x)))
(make-ratio x1 (* x2 y))))
(defmethod // ((x <integer>) (y <rational>))
(let ((y1 (numerator y)) (y2 (denominator y)))
(make-ratio (* x y2) y1)))
(defmethod // ((x <integer>) (y <integer>))
(make-ratio x y))
;;; 逆数
(defmethod inv ((x <integer>)) (make-ratio 1 x))
(defmethod inv ((x <rational>))
(make-ratio (denominator x) (numerator x)))
;;; 有理数の比較
(defun compare-ratio (x y)
(let ((x1 (numerator x)) (x2 (denominator x))
(y1 (numerator y)) (y2 (denominator y)))
(- (* x1 y2) (* x2 y1))))
;;; 比較演算子の定義
(defmethod =/ ((x <rational>) (y <rational>))
(= (compare-ratio x y) 0))
(defmethod /=/ ((x <rational>) (y <rational>))
(not (= (compare-ratio x y) 0)))
(defmethod </ ((x <rational>) (y <rational>))
(< (compare-ratio x y) 0))
(defmethod >/ ((x <rational>) (y <rational>))
(> (compare-ratio x y) 0))
(defmethod <=/ ((x <rational>) (y <rational>))
(<= (compare-ratio x y) 0))
(defmethod >=/ ((x <rational>) (y <rational>))
(>= (compare-ratio x y) 0))
;;; 整数の判定
(defmethod is-integer ((x <rational>))
(= (denominator x) 1))
;;; 整数に変換
(defmethod to-integer ((x <rational>))
(div (numerator x) (denominator x)))
;;; 浮動小数点数に変換
(defmethod to-float ((x <rational>))
(quotient (numerator x) (denominator x)))
;;; 文字列に変換
(defmethod to-string ((x <rational>))
(string-append (convert (numerator x) <string>) "/" (convert (denominator x) <string>)))
;;; 表示
(defmethod display/ ((x <rational>))
(format (standard-output) "~D/~D" (numerator x) (denominator x)))
;;; 検索
(defun find-index (x xs i)
(cond
((null xs) -1)
((eql x (car xs)) i)
(t
(find-index x (cdr xs) (+ i 1)))))
;;; 循環小数 m/n = ((...) (...))
(defmethod repeat-decimal ((x <rational>))
(let ((m (numerator x))
(n (denominator x))
(xs nil) (ys nil) (zs nil))
(while (not zs)
(let ((p (div m n))
(q (mod m n)))
(setq ys (cons p ys))
(cond
((= q 0)
(setq zs (list (reverse ys) '(0))))
(t
(let ((i (find-index q xs 0)))
(cond
((>= i 0)
(setq zs (list (reverse (subseq ys (+ i 1) (length ys)))
(reverse (subseq ys 0 (+ i 1))))))
(t
(setq xs (cons q xs))
(setq m (* q 10)))))))))
zs))
;;; 循環小数を分数に直す
(defun digit-to-number (xs a)
(if (null xs)
a
(digit-to-number (cdr xs) (+ (* a 10) (car xs)))))
(defmethod from-repeat-decimal ((xs <list>) (ys <list>))
(let ((p0 (digit-to-number xs 0))
(q0 (expt 10 (- (length xs) 1)))
(p1 (digit-to-number ys 0))
(q1 (- (expt 10 (length ys)) 1)))
(// (+ (* q1 p0) p1) (* q0 q1))))
ISLisp>(load "rational.lsp") T ISLisp>(defglobal a (// 1 2)) A ISLisp>(display/ a) 1/2NIL ISLisp>(rationalp a) T ISLisp>(to-string a) "1/2" ISLisp>(defglobal b (// 1 3)) B ISLisp>(display/ b) 1/3NIL ISLisp>(display/ (+/ a b)) 5/6NIL ISLisp>(display/ (-/ a b)) 1/6NIL ISLisp>(display/ (-/ b a)) -1/6NIL ISLisp>(display/ (*/ a b)) 1/6NIL ISLisp>(display/ (// a b)) 3/2NIL ISLisp>(display/ (// b a)) 2/3NIL ISLisp>(=/ a b) NIL ISLisp>(=/ a a) T ISLisp>(/=/ a b) T ISLisp>(/=/ a a) NIL ISLisp>(</ a b) NIL ISLisp>(</ b a) T ISLisp>(>/ a b) T ISLisp>(>/ b a) NIL ISLisp>(is-integer a) NIL ISLisp>(is-integer (+/ a a)) T ISLisp>(to-integer (+/ a a)) 1 ISLisp>(to-integer (+/ a b)) 0 ISLisp>(to-integer (// a b)) 1 ISLisp>(to-float (+/ a b)) 0.833333 ISLisp>(to-float (// a b)) 1.5 ISLisp>(repeat-decimal (// 1 2)) ((0 5) (0)) ISLisp>(repeat-decimal (// 1 3)) ((0) (3)) ISLisp>(repeat-decimal (// 1 7)) ((0) (1 4 2 8 5 7)) ISLisp>(repeat-decimal (// 355 113)) ((3) (1 4 1 5 9 2 9 2 0 3 5 3 9 8 2 3 0 0 8 8 4 9 5 5 7 5 2 2 1 2 3 8 9 3 8 0 5 3 0 9 7 3 4 5 1 3 2 7 4 3 3 6 2 8 3 1 8 5 8 4 0 7 0 7 9 6 4 6 0 1 7 6 9 9 1 1 5 0 4 4 2 4 7 7 8 7 6 1 0 6 1 9 4 6 9 0 2 6 5 4 8 6 7 2 5 6 6 3 7 1 6 8)) ISLisp>(display/ (apply #'from-repeat-decimal (repeat-decimal (// 1 2)))) 1/2NIL ISLisp>(display/ (apply #'from-repeat-decimal (repeat-decimal (// 1 3)))) 1/3NIL ISLisp>(display/ (apply #'from-repeat-decimal (repeat-decimal (// 1 7)))) 1/7NIL ISLisp>(display/ (apply #'from-repeat-decimal (repeat-decimal (// 355 113)))) 355/113NIL
それでは rational.lsp の簡単な例題として、分数を使ったパズルを紹介しましょう。rational.lsp を使うと、簡単に解法プログラムを作ることができます。
それでは問題です。
下図の A から I の場所に 1 から 9 までの数字をひとつずつ配置します。3 つの分数を足すと 1 / N になる配置を求めてください。
このパズルの元ネタは N = 1 の場合で、参考文献『超々難問数理パズル 解けるものなら解いてごらん』に掲載されています。ちなみに、3 つの分数の和が整数になる場合、その値は 1 しかありません。また、値が 1 / N (N は整数) になる場合は 2, 3, 4, 6, 10 の 5 通りです。
プログラムは次のようになります。
リスト : パズル「小町分数」の解法
(load "combination.lsp") ; Easy-ISLisp は (import "combination") とする
(load "rational.lsp")
(defun check (a b c d e f g h i)
(let ((x (// a (+ (* b 10) c)))
(y (// d (+ (* e 10) f)))
(z (// g (+ (* h 10) i))))
(if (and (</ x y) (</ y z))
(let ((s (+/ (+/ x y) z)))
(if (= (numerator s) 1)
(format (standard-output) "~D/~D~D + ~D/~D~D + ~D/~D~D = ~A~%"
a b c d e f g h i (to-string s)))))))
(defun solver ()
(permutation (lambda (xs) (apply #'check xs)) 9 '(1 2 3 4 5 6 7 8 9)))
拙作のライブラリ combination.lsp の関数 permutation を使って順列を生成し、分数の和が 1 / N になる数字を探します。単純な生成検定法ですが、分数 x, y, z を x < y < z に限定することで、重複解を生成しないように工夫しています。それから、このプログラムでは 3 つの分数の和が 1 になる場合も解を出力します。プログラムを実行するときはご注意くださいませ
ISLisp>(load "komachi_ratio.lsp") T ISLisp>(solver) 1/24 + 3/56 + 7/98 = 1/6 1/26 + 7/84 + 5/39 = 1/4 1/32 + 5/96 + 7/84 = 1/6 1/48 + 7/96 + 5/32 = 1/4 1/56 + 3/72 + 9/84 = 1/6 1/96 + 5/48 + 7/32 = 1/3 1/96 + 7/84 + 5/32 = 1/4 2/95 + 1/38 + 4/76 = 1/10 3/48 + 9/72 + 5/16 = 1/2 3/54 + 6/72 + 9/81 = 1/4 4/57 + 2/19 + 6/38 = 1/3 5/63 + 2/18 + 7/49 = 1/3 7/68 + 5/34 + 9/12 = 1/1 NIL
もうひとつ「小町分数」を出題しましょう。
下図の A から I の場所に 1 から 9 までの数字をひとつずつ配置します。3 つの分数を足すと 1 / 2 になる配置を求めてください。
このパズルの元ネタも値が 1 になる場合で、参考文献『超々難問数理パズル 解けるものなら解いてごらん』に掲載されています。この問題で値が 1 / N (N は整数) になる場合は 1 と 2 の 2 通りしかないようです。
リスト : 小町分数 (2)
(defun check2 (a b c d e f g h i)
(let ((x (// a (+ (* b c))))
(y (// d (+ (* e f))))
(z (// g (+ (* h i)))))
(if (and (< a d) (< d g) (< b c) (< e f) (< h i))
(let ((s (+/ (+/ x y) z)))
(if (= (numerator s) 1)
(format (standard-output) "~D/(~D*~D) + ~D/(~D*~D) + ~D/(~D*~D) = ~A~%"
a b c d e f g h i (to-string s)))))))
(defun solver2 ()
(permutation (lambda (xs) (apply #'check2 xs)) 9 '(1 2 3 4 5 6 7 8 9)))
ISLisp>(solver2) 1/(2*4) + 5/(3*6) + 7/(8*9) = 1/2 1/(3*6) + 5/(8*9) + 7/(2*4) = 1/1 NIL
連分数 (continued fraction) は、分数の分母にさらに分数が含まれる形をした分数のことです。
連分数の中で、分子 \(c_1, c_2, \ldots\) がすべて 1 で、\(b_0\) が整数、\(b_1, b_2, \ldots\) がすべて正の整数であるような連分数を「正則連分数」といいます。正則連分数は \(b_i\) の情報を保持するだけで、連分数に復元することができます。そこで、以下の右辺のように記述することが多いようです。
有理数 (分数) は「ユークリッドの互除法」を使って正則連分数に展開することができます。
\(a, b\) の問題を \(b, r\) の問題に帰着させるのは「ユークリッドの互除法」と同じです。この計算を繰り返し行うと、\(a, b\) はどんどん小さくなっていき、\(r = 0\) になったとき連分数展開が終了します。
簡単な例を示します。
OK!-ISLisp で検算してみましょう。
ISLisp>(load "rational.lsp") T ISLisp>(display/ (+/ 1 (inv (+/ 1 (inv (+/ 1 (inv 5))))))) 17/11NIL ISLisp>(display/ (+/ 3 (inv (+/ 7 (inv 16))))) 355/113NIL
連分数に正しく展開されていますね。
プログラムは簡単です。次のリストを見てください。
リスト : 有理数を正則連分数に展開する
(defun cont-frac (x)
(if (not (rationalp x))
(error "must be rational")
(for ((a (numerator x))
(b (denominator x))
(xs nil))
((<= b 0) (reverse xs))
(let ((p (div a b))
(q (mod a b)))
(setq xs (cons p xs))
(setq a b)
(setq b q)))))
関数 cont-frac は引数 x を正則連分数展開します。最初に引数 x が有理数か rationalp でチェックします。そうであれば、分子を変数 a に、分母を変数 b にセットします。展開した結果はリスト xs にセットします。あとは、ユーグリッドの互除法と同じです。b が 0 より大きければ、div と mod で a と b の商と剰余を求めて変数 p, q にセットします。そして、p を xs に追加し、a を b に、b を q に更新します。最後に、xs を reverse で反転して返します。
リスト : 正則連分数を有理数に戻す
(defun cont-frac-reduce (xs)
(if (null (cdr xs))
(car xs)
(+/ (car xs)
(inv (cont-frac-reduce (cdr xs))))))
;;; 畳み込み
(defun foldr (f xs)
(if (null (cdr xs))
(car xs)
(funcall f (car xs) (foldr f (cdr xs)))))
;;; 畳み込みを使う
(defun cont-frac-reduce1 (xs)
(foldr (lambda (x a) (+/ x (inv a))) xs))
関数 cont-frac-reduce は引数の正則連分数を通常の分数に直します。アルゴリズムは簡単です。リスト xs の要素がひとつであれば、その要素を返します。そうでなければ、cont-frac-reduce を再帰呼び出しして、その返り値の逆数と xs の先頭要素の足し算を返します。xs の先頭要素を \(x\) とし、cont-frac-reduce の返り値を \(a\) とすると、これで連分数 \(x + \frac{1}{a}\) を求めることができます。あとは、xs の後ろから順番に連分数を求めていくだけです。cont-frac-reduce1 は畳み込み foldr を使ったバージョンです。
なお、参考 URL 『ペル方程式に関する基本的な性質まとめ』によると、連分数 \([a_0; a_1, a_2, \ldots, a_{n-2}, a_{n-1}]\) を約分した有理数 \(\frac{p_n}{q_n}\) の分子と分母 \(p_n, q_n\) は、以下に示す漸化式で求めることができるそうです。
ISLisp でプログラムすると、次のようになります。
リスト : 連分数を有理数に約分する (2)
;;; 畳み込み
(defun foldl (f a xs)
(if (null xs)
a
(foldl f (funcall f a (car xs)) (cdr xs))))
;;; 漸化式を使う
(defun cont-frac-sub (xs a0 a1)
(cdr (foldl
(lambda (a x) (cons (cdr a) (+ (* x (cdr a)) (car a))))
(cons a0 a1)
(cdr xs))))
(defun cont-frac-reduce2 (xs)
(// (cont-frac-sub xs 1 (car xs)) (cont-frac-sub xs 0 1)))
関数 cont-frac-sub は漸化式を計算します。これも畳み込み foldl を使うと簡単です。関数 cont-frac-reduce2 は cont-frac-sub を 2 回呼び出して分子 p と分母 q を求めて、p / q を計算して返すだけです。
簡単な実行例を示します。
ISLisp>(cont-frac (// 17 11)) (1 1 1 5) ISLisp>(cont-frac (// -17 11)) (-2 2 5) ISLisp>(cont-frac (// 355 113)) (3 7 16) ISLisp>(display/ (cont-frac-reduce '(1 1 1 5))) 17/11NIL ISLisp>(display/ (cont-frac-reduce '(-2 2 5))) -17/11NIL ISLisp>(display/ (cont-frac-reduce '(3 7 16))) 355/113NIL ISLisp>(display/ (cont-frac-reduce1 '(1 1 1 5))) 17/11NIL ISLisp>(display/ (cont-frac-reduce1 '(-2 2 5))) -17/11NIL ISLisp>(display/ (cont-frac-reduce1 '(3 7 16))) 355/113NIL ISLisp>(display/ (cont-frac-reduce2 '(1 1 1 5))) 17/11NIL ISLisp>(display/ (cont-frac-reduce2 '(-2 2 5))) -17/11NIL ISLisp>(display/ (cont-frac-reduce2 '(3 7 16))) 355/113NIL ISLisp>(display/ (cont-frac-reduce '(1 2 3 4 5 6 7 8))) 81201/56660NIL ISLisp>(cont-frac (// 81201 56660)) (1 2 3 4 5 6 7 8) ISLisp>(display/ (cont-frac-reduce1 '(1 2 3 4 5 6 7 8))) 81201/56660NIL ISLisp>(display/ (cont-frac-reduce2 '(1 2 3 4 5 6 7 8))) 81201/56660NIL
正常に動作していますね。
有理数の連分数展開は有限回で終了しますが、無理数の連分数展開は無限に続きます。たとえば、\(\sqrt 2\) の連分数展開を求めてみましょう。
係数が有理数の二次方程式の根を「二次無理数」といいます。二次無理数の正則連分数展開は循環することが知られています。
実際に試してみましょう。
ISLisp>(abs (- (to-float (cont-frac-reduce '(1 2 2 2 2))) (sqrt 2))) 0.000420459 ISLisp>(abs (- (to-float (cont-frac-reduce '(1 2 2 2 2 2 2))) (sqrt 2))) 1.23789e-005 ISLisp>(abs (- (to-float (cont-frac-reduce '(1 2 2 2 2 2 2 2 2))) (sqrt 2))) 3.64404e-007 ISLisp>(abs (- (to-float (cont-frac-reduce '(1 1 2 1 2))) (sqrt 3))) 0.00477808 ISLisp>(abs (- (to-float (cont-frac-reduce '(1 1 2 1 2 1 2))) (sqrt 3))) 0.00034349 ISLisp>(abs (- (to-float (cont-frac-reduce '(1 1 2 1 2 1 2 1 2))) (sqrt 3))) 2.46638e-005 ISLisp>(abs (- (to-float (cont-frac-reduce '(2 4 4 4 4))) (sqrt 5))) 2.40373e-006 ISLisp>(abs (- (to-float (cont-frac-reduce '(2 4 4 4 4 4 4))) (sqrt 5))) 7.46507e-009 ISLisp>(abs (- (to-float (cont-frac-reduce '(2 4 4 4 4 4 4 4 4))) (sqrt 5))) 2.31837e-011 ISLisp>(abs (- (to-float (cont-frac-reduce '(2 1 1 1 4))) (sqrt 7))) 0.00289417 ISLisp>(abs (- (to-float (cont-frac-reduce '(2 1 1 1 4 1 1 1 4))) (sqrt 7))) 1.14008e-005 ISLisp>(abs (- (to-float (cont-frac-reduce '(2 1 1 1 4 1 1 1 4 1 1 1 4))) (sqrt 7))) 4.48856e-008
浮動小数点数 \(\alpha\) の正則連分数展開は次のアルゴリズムで求めることができます。
プログラムは次のようになります。
リスト : 浮動小数点数を正則連分数に展開する
(defun cont-frac-flt (x k e)
(block exit
(for ((k k (- k 1))
(xs nil))
((= k 0) (reverse xs))
(let* ((p (floor x))
(q (- x p)))
(setq xs (cons p xs))
(cond
((< (abs q) e)
(return-from exit (reverse xs)))
(t
(setq x (quotient 1 q))))))))
引数 x が連分数展開を行う浮動小数点数、k が段数、e が 0 と判定する基準値です。浮動小数点数は不正確な数なので、演算誤差が発生します。k の値を増やしても、規則性は無限に続かずに途中で失われることがあります。
また、0.5 や 1.25 など浮動小数点数でも正確に表現できる場合、途中で q の値は 0 になるので、そこで展開を終了します。ただし、演算誤差が生じる場合があるので、ぴったり 0 になるとは限りません。そこで、\(|q| \lt e\) よりも小さくなったならば、0 と判定して展開を終了します。
それでは実際に試してみましょう。
ISLisp>(cont-frac-flt (sqrt 2) 8 1e-16) (1 2 2 2 2 2 2 2) ISLisp>(cont-frac-flt (sqrt 3) 8 1e-16) (1 1 2 1 2 1 2 1) ISLisp>(cont-frac-flt (sqrt 5) 8 1e-16) (2 4 4 4 4 4 4 4) ISLisp>(cont-frac-flt (sqrt 7) 8 1e-16) (2 1 1 1 4 1 1 1) ISLisp>(cont-frac-flt (sqrt 7) 16 1e-16) (2 1 1 1 4 1 1 1 4 1 1 1 4 1 1 1) ISLisp>(cont-frac-flt 0.5 8 1e-16) (0 2) ISLisp>(cont-frac-flt 0.25 8 1e-16) (0 4) ISLisp>(cont-frac-flt 0.125 8 1e-16) (0 8) ISLisp>(cont-frac-flt 1.1 8 1e-16) (1 9 1 112589990684262 2 2 6 46912496118442) ISLisp>(cont-frac-flt 1.1 8 1e-14) (1 9 1) ISLisp>(display/ (cont-frac-reduce '(1 9 1))) 11/10NIL
正常に動作しているようです。興味のある方はいろいろ試してみてください。
;;;
;;; contfrac.lsp : 連分数 (ISLisp 用)
;;;
;;; Copyright (c) 2024 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(load "rational.lsp")
;;; 分数を連分数に展開する
(defun cont-frac (x)
(if (not (rationalp x))
(error "must be rational")
(for ((a (numerator x))
(b (denominator x))
(xs nil))
((<= b 0) (reverse xs))
(let ((p (div a b))
(q (mod a b)))
(setq xs (cons p xs))
(setq a b)
(setq b q)))))
;;; 連分数を分数に変換する
;;; 単純な再帰呼び出し
(defun cont-frac-reduce (xs)
(if (null (cdr xs))
(car xs)
(+/ (car xs)
(inv (cont-frac-reduce (cdr xs))))))
;;; 畳み込み
(defun foldr (f xs)
(if (null (cdr xs))
(car xs)
(funcall f (car xs) (foldr f (cdr xs)))))
(defun foldl (f a xs)
(if (null xs)
a
(foldl f (funcall f a (car xs)) (cdr xs))))
;;; 畳み込みを使う
(defun cont-frac-reduce1 (xs)
(foldr (lambda (x a) (+/ x (inv a))) xs))
;;; 漸化式を使う
(defun cont-frac-sub (xs a0 a1)
(cdr (foldl
(lambda (a x) (cons (cdr a) (+ (* x (cdr a)) (car a))))
(cons a0 a1)
(cdr xs))))
(defun cont-frac-reduce2 (xs)
(// (cont-frac-sub xs 1 (car xs)) (cont-frac-sub xs 0 1)))
;;; 浮動小数点数を正則連分数に展開する
(defun cont-frac-flt (x k e)
(block exit
(for ((k k (- k 1))
(xs nil))
((= k 0) (reverse xs))
(let* ((p (floor x))
(q (- x p)))
(setq xs (cons p xs))
(cond
((< (abs q) e)
(return-from exit (reverse xs)))
(t
(setq x (quotient 1 q))))))))