Fractionで平方根近似
非正則連分数で素数の平方根を近似しよう
1.√2を分数を入れ子にして表す
分数(フラクション)を入れ子にした
連分数(continued fraction)というのは知ってますね。
最近の言語では分数パッケージがあるので、その使い方になれるという目的もかねて、
連分数で平方根を近似してみよう。
それを連分数展開(continued fraction expansion)と言うこともある。
たとえば、1+1/(2+1/(2+1/(2+1/......)))
は、割り算を分数にすると、無限につながる連分数の式になる。
この値は2の平方根の近似値になることが知られているね。
分子はいつも1なので、「1/(a」のaの部分と先頭の整数部分だけ決めればよいので、
2の平方根の連分数のことを[1,2,2,2,2,......]と書いたり、
2がサイクル1で続くだけなので、[1;2]などと書いたりできるね。
質問:√2の近似値を連分数を使ってpython,juliaで求めるにはどうしますか。
まずpythonで分数計算と小数表示をしてよう。
#[IN]Python
#===========================================================
#1+1/(2+1/(2+1/(2+1/......)))
#Fraction(numerator,denominator)は
#numerator,denominatorで分数オブジェクトを作るコンストラクター。
#整数の1、2を分数のone,twoとして作っておいて分数計算とする。
from fractions import Fraction
one=Fraction(1,1)
two=Fraction(2,1)
apx=one+one/(two+one/(two+one/two))
print(apx,":",float(apx))
apx2=one+one/(two+one/(two+one/(two+one/(two+one/(two+one/(two+one/(two+one/(two+one/two))))))))
print(apx2,":",float(apx2))
#===========================================================
[OUT]
17/12 : 1.4166666666666667
3363/2378 : 1.4142136248948696
Juliaではどうでしょうか。パッケージを宣言せずに、分数の線として//を使うだけで、
お手軽に分数計算ができたね。
#[IN]Julia
#===========================================================
one=1//1
two=2//1
apx=one+one/(two+one/(two+one/two))
println(apx,":",float(apx))
apx2=one+one/(two+one/(two+one/(two+one/(two+one/(two+one/(two+one/(two+one/(two+one/two))))))))
println(apx2,":",float(apx2))
#============================================================
[OUT]
17//12:1.4166666666666667
3363//2378:1.4142136248948696
2.回数指定で√2を近似する関数
上記のやり方では、繰り返しを
ベタで、式にしている。
もっと、自由に使えるものはできないだろうか。
質問:√2の連分数展開を関数にして、好きな回数で近似するにはどうしたよいか?
関数fで整数nから分数n/1を作るようにすると、
f(1),f(2)とかくだけで、整数を分数に直せるね。
#[IN]Python
#=========================
from fractions import Fraction
f=lambda n:Fraction(n,1)
def continued_fraction(n):
part = f(2)
for i in range(1,n+1):
part= f(2)+f(1)/part
return f(1)+f(1)/part
for i in range(1,20):
p=continued_fraction(i)
print(float(p),":",p)
#=========================
[OUT]
1.4 : 7/5
1.4166666666666667 : 17/12
1.4137931034482758 : 41/29
1.4142857142857144 : 99/70
1.4142011834319526 : 239/169
1.4142156862745099 : 577/408
1.4142131979695431 : 1393/985
1.4142136248948696 : 3363/2378
1.4142135516460548 : 8119/5741
1.4142135642135643 : 19601/13860
1.4142135620573204 : 47321/33461
1.4142135624272734 : 114243/80782
1.4142135623637995 : 275807/195025
1.4142135623746899 : 665857/470832
1.4142135623728214 : 1607521/1136689
1.414213562373142 : 3880899/2744210
1.414213562373087 : 9369319/6625109
1.4142135623730965 : 22619537/15994428
1.4142135623730947 : 54608393/38613965
#[IN]Julia
#=========================
f = n -> n//1
function continued_fraction(n)
part = f(2)
for i in 1:n
part= f(2)+f(1)/part
end
return f(1)+f(1)/part
end
for i in 1:20
p=continued_fraction(i)
println(float(p),":",p)
end
#=========================
[OUT]
1.4:7//5
1.4166666666666667:17//12
1.4137931034482758:41//29
1.4142857142857144:99//70
1.4142011834319526:239//169
1.4142156862745099:577//408
1.4142131979695431:1393//985
1.4142136248948696:3363//2378
1.4142135516460548:8119//5741
1.4142135642135643:19601//13860
1.4142135620573204:47321//33461
1.4142135624272734:114243//80782
1.4142135623637995:275807//195025
1.4142135623746899:665857//470832
1.4142135623728214:1607521//1136689
1.414213562373142:3880899//2744210
1.414213562373087:9369319//6625109
1.4142135623730965:22619537//15994428
1.4142135623730947:54608393//38613965
1.4142135623730951:131836323//93222358
3.一般の平方根を近似する関数
√2だけの近似だけで使い道が限られるね。
そこで、もっとさまざまな平方根を求められるようにしてみよう。
√2の連分数展開は[1;2]で、サイクル1だったが、他の平方根ではサイクルがいろいろる。
どうしてそうなるかはさておき、連分数展開を1つの分数や小数に直す関数を作ってみよう。
調べたい分数の長さをcntとしてサイクルを回して、分数の全長をcntにしたdistを作ろう。
有限な連分数の計算順位は最後尾が最優先になるので、逆順のrevを作ろう。
あとは、逆数にしてリストの数をたす。
これをくりかえそう。これで1未満のパートのpartができる。
最後のDataの先頭にある整数部分を最後にたそう。
#[IN]Python
#=================================================================
from fractions import Fraction
from itertools import cycle
f = lambda n:Fraction(n,1)
#(n,[cycle])
datum = [(1,[2]),(1,[1,2]),(2,[4]),(2,[1,1,1,4]),(3,[3,6]),(3,[1,1,1,1,6]),(4,[8]),(4,[2,1,3,1,2,8])]
D =[2,3,5,7,11,13,17,19]
def continued_fraction(Data,cnt):
n,lst = Data[0],Data[1]
dist = []
for i in cycle(lst):
dist.append(i)
if len(dist)>cnt-1 :
break
rev=list(reversed(dist))
print(dist,"-->",rev)
part=f(1)/f(rev[0])
for i in range(1,cnt):
part=f(rev[i])+part
part=f(1)/part
return f(n)+part
cnt=20
for i in range(len(D)):
p=continued_fraction(datum[i],cnt)
print(float(p),":",p,":√",D[i])
#=================================================================
[OUT]
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] --> [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
1.4142135642135643 : 19601/13860 :√ 2
[1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1] --> [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1]
1.7320512820512821 : 1351/780 :√ 3
[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4] --> [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
2.236067977499794 : 16692641/7465176 :√ 5
[1, 1, 1, 4, 1, 1, 1, 4, 1, 1, 1] --> [1, 1, 1, 4, 1, 1, 1, 4, 1, 1, 1]
2.645751633986928 : 2024/765 :√ 7
[3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3] --> [3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3]
3.3166247903554016 : 31521799/9504180 :√ 11
[1, 1, 1, 1, 6, 1, 1, 1, 1, 6, 1] --> [1, 6, 1, 1, 1, 1, 6, 1, 1, 1, 1]
3.6055514974433893 : 4936/1369 :√ 13
[8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8] --> [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8]
4.123105625617661 : 41270070401/10009462320 :√ 17
[2, 1, 3, 1, 2, 8, 2, 1, 3, 1, 2] --> [2, 1, 3, 1, 2, 8, 2, 1, 3, 1, 2]
4.3588989441930615 : 57799/13260 :√ 19
juliaでもほとんど変わらない。
#[IN]Julia
#=================================================================
f = n -> n//1
#(n,[cycle])
datum = [(1,[2]),(1,[1,2]),(2,[4]),(2,[1,1,1,4]),(3,[3,6]),(3,[1,1,1,1,6]),(4,[8]),(4,[2,1,3,1,2,8])]
D =[2,3,5,7,11,13,17,19]
function continued_fraction(Data,cnt)
(n,lst) = Data
dist=[ ]
for (i, v) in enumerate(Iterators.cycle(lst))
push!(dist,v)
i > cnt-1 && break
end
rev=reverse(dist)
println(dist,"-->",rev)
part=f(1)/f(rev[1])
for i in 2:cnt
part=f(rev[i])+part
part=f(1)/part
end
return f(n)+part
end
cnt=20
for i in 1:length(D)
p=continued_fraction(datum[i],cnt)
println(float(p),":",p,":√",D[i])
end
#=================================================================
[OUT]
Any[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]-->Any[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
1.4142135623730947:54608393//38613965:√2
Any[1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2]-->Any[2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1]
1.732050807565499:716035//413403:√3
Any[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]-->Any[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
2.23606797749979:7331474697802//3278735159921:√5
Any[1, 1, 1, 4, 1, 1, 1, 4, 1, 1, 1, 4, 1, 1, 1, 4, 1, 1, 1, 4]-->Any[4, 1, 1, 1, 4, 1, 1, 1, 4, 1, 1, 1, 4, 1, 1, 1, 4, 1, 1, 1]
2.645751311063895:2388325//902702:√7
Any[3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6]-->Any[6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3]
3.3166247903554:31539640338297//9509559365899:√11
Any[1, 1, 1, 1, 6, 1, 1, 1, 1, 6, 1, 1, 1, 1, 6, 1, 1, 1, 1, 6]-->Any[6, 1, 1, 1, 1, 6, 1, 1, 1, 1, 6, 1, 1, 1, 1, 6, 1, 1, 1, 1]
3.6055512754637564:5564523//1543321:√13
Any[8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8]-->Any[8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8]
4.123105625617661:6355271576489378132//1541379764079492545:√17
Any[2, 1, 3, 1, 2, 8, 2, 1, 3, 1, 2, 8, 2, 1, 3, 1, 2, 8, 2, 1]-->Any[1, 2, 8, 2, 1, 3, 1, 2, 8, 2, 1, 3, 1, 2, 8, 2, 1, 3, 1, 2]
4.358898943540674:512445947//117563163:√19
4。Dから√Dの連分数情報を作り出す。
これまでは、連分数情報をリストで与えると、それから平方根の近似値を分数、小数で表示する関数を考えてきた。
しかし、そもそも、その情報を作り出すことができないと、情報を入手して手入力したり、文字情報を数値情報に変換するなどの手間がかかってしまう。
Dだけから、√Dの連分数情報を作り出すにはどうしたらよいだろうか。
作り方に着目してみよう。
たとえば√2=1+1/(2+1/(2+1/(2+........)))だったから、[1;2]を得た。
有限分数99/70では、99/70=1+29/70=1+1/70/29=1+1/(2+12/29)
=1+1/(2+1/29/12)=1+1/(2+1/(2+5/12))=1+1/(2+1/(2+1/(2+2/5))=1+1/(2+1/(2+1/(2+1/(2+1/2))))
これを無理数√2でやると、
√2/1=1+(√2-1)/1=1+1/(√2+1)=1+1/(2+(√2-1))=1+1/(2+1/(√2+1))=.....
もとの数A/Bを整数部分+逆数にして、分数部分をまた整数部分+逆数にしてという操作は
AとBについてユークリッドの互除法をやっていると同じだとわかるね。
手計算で逆数を有理化していけば、単位分数を作っていけば、連分数情報が得られることがわかるね。
しかし、いちいち、逆数にして有理化するのはコード作成が大変そうだ。
そこで、サイクルの規則性についてしらべよう。
<D=n2+1のとき>
Dが平方数n2に1をたした形で、[ n;(2n)]
例)D=2 ならn=1から [1;(2)]
D=5 ならn=2から [2;(4)]
D=10 ならn=3から [3;(6)]
D=17 ならn=4から [4;(8)]
D=26 ならn=5から [5;(10)]
D=37 ならn=6から [6;(12)]
理由)
x=√(n2+1)=n+(√(n2+1) -n) =n + 1/x1とする。
x1=1/(√(n2+1) -n)=(√(n2+1)+n)=2n+(√(n2+k)-n)
= d + 1/x2とおける。だから、サイクルの先頭は2nだね。
x2=1/(√(n2+1)-n)=x1となるから、サイクル長は1になるね。
<D=n2+kでd=2n/kが整数のとき>
Dが平方数n2にkをたした形で、d=2n/kが整数なら、[n;(d, 2n)]
例)D=3 ならn=1, k=1から2n/k=1 サイクル=(1,2)
D=11ならn=3, k=2から2n/k=3 サイクル=(3,6)
これは、Dが素数じゃなくても使える。
D=6ならn=2,k=2から2n/k=2 サイクル(2,4)
D= 8ならn=2,k=4から2n/k=1 サイクル(1,4)
D=12ならn=3,k=3から2n/k=2 サイクル(2,6)
D=15ならn=3,k=6から2n/k=1 サイクル(1,6)
D=18ならn=4,k=2から2n/k=4 サイクル(4,8)
D=20ならn=4,k=4から2n/k=2 サイクル(2,8)
D=24ならn=4,k=8から2n/k=1 サイクル(1,8)
理由)
x=√(n2+k)=n+(√(n2+k) -n) =n + 1/x1とする。
x1=1/(√(n2+k) -n)=(√(n2+k)+n)/(√(n2+k) +n)(√(n2+k) -n)=(√(n2+k)+n)/k=2n/k+(√(n2+k)-n)/k
= d + 1/x2とおける。だから、サイクルの先頭はdだね。
x2=k/(√(n2+k)-n)=k(√(n2+k)+n)/(√(n2+k)-n)(√(n2+k)+n)=k(√(n2+k)+n)/k=√(n2+k)+n
=2n+√(n2+k)-n=2n+1/x3とおける。
x3=1/(√(n2+k) -n)=x1となるから、2nがサイクルの末尾になる。これで周期は2となる。
<D=n2-kでd=2n/kが整数のとき>
Dが平方数n2にkをたした形で、d=2n/kが整数なら、[ n-1;(1,d-2,1, 2(n-1))]
例)D=7 ならn=3, k=2からd=2n/k=3 。これからn-1=2, d-2=1より、 [2;(1,1,1,4)]
D=14ならn=4, k=2からd=2n/k=4 。これからn-1=3, d-2=2より、 [3;(1,2,1,6)]
D=23ならn=5, k=2からd=2n/k=5 。これからn-1=4, d-2=3より、 [4;(1,3,1,8)]
D=32ならn=6, k=4からd=2n/k=3 。これからn-1=5, d-2=1より、 [5;(1,1,1,10)]
D=33ならn=6, k=3からd=2n/k=4 。これからn-1=5, d-2=2より、 [5;(1,2,1,10)]
D=34ならn=6, k=2からd=2n/k=6 。これからn-1=5, d-2=4より、 [5;(1,4,1,10)]
D=47ならn=7, k=2からd=2n/k=7 。これからn-1=6, d-2=5より、 [6;(1,5,1,12)]
D=32ならn=4, k=2からd=2n/k=4 。これからn-1=3, d-2=2より、 [3;(1,2,1,6)
理由)
上記と同様の計算をすると、x5=x1となり、サイクル長が4になる。
<D=n2+1のとき>
Dが平方数n2から1を引いた形で、[ n-1;(1,2(n-1))]
例)D=3 ならn=2から [1;(1,2)]
D=8 ならn=3から [2;(1,4)]
D=15 ならn=4から [3;(1,6)]
D=24 ならn=5から [4;(1,8)]
D=35 ならn=6から [5;(1,10)] D=48 ならn=7から [6;(1,12)]
理由)
上記と同様の計算で、k=1にすると、d=2n/k=2nより、d-2=2n-2=2(n-1)となるので。
次に1にもどる。
このように、Dに近い平方数nに着目することで、規則性が使えるが、2n/kが整数にならない場合がある。
D=13,19,21,22,28,29,.....などは上記のタイプからはずれるので、規則性につながらない。
5.非正則な連分数で平方根を近似する
これまでの連分数は逆数利用で、分子を1とする形に限定してきた。
しかし、そうすることで見えてくるおもしろさはあるが、平方根の近似値を求めるための規則性としては
あまり、単純化は簡単そうではない。
そこで、上記のような分子が1ではない形でのくり返しをしてみよう。
√n=√n(m+√n)/(m+√n)=(n+m√n)/(m+√n)=(m2+m√n+n-m2)/(m+√n)=(m(m+√n)+(n-m2))/(m+√n)
= m + (n-m2)/(m + √n)
と変形できる。
たとえば、
n=11なら、3^2<11<4^2から、m=3。t=n-m^2=2。2m=6から、
√11=3+(√11-3)=(3+2/(3+√11))となる。 √11=3+2/(3+(3+2/(3+√11)))==> 3+2/(6+2/(6+2/....))
すると、
part=2/6, part=2/(6+part)をくり返し、最後に3+partする。
一般には、
mはsqr(n)の整数部分。t=n-m^2とすると、
part=t/2m, part=t/(2m+part)をくり返し、最後にm+partをする。
質問:20以下の素数の平方根の近似値を求めるにはどんなコードをかきますか?
#[IN]python
#============================================
from fractions import Fraction
f = lambda n:Fraction(n,1)
Ds = [2,3,5,7,11,13,17,19] #([n])
def irregular_fraction(n ,cnt):
m=0
while n-m**2>0:
m +=1
break
t = n-m**2
part=f(t)/f(2*m)
for i in range(1,cnt):
part=f(t)/(f(2*m)+part)
return f(m)+part
cnt2=20
cnt=11
for i in range(len(Ds)):
q=irregular_fraction(Ds[i],cnt2)
print(float(q),":",q,":非正則",Ds[i])
p=continued_fraction(datum[i],cnt)
print(float(p),":",p,":正則",D[i])
#============================================
[OUT]
1.4142135623730947 : 54608393/38613965 :非正則 2
1.4142135642135643 : 19601/13860 :正則 2
1.732050807565499 : 716035/413403 :非正則 3
1.7320512820512821 : 1351/780 :正則 3
2.236067970034716 : 12238/5473 :非正則 5
2.236067977499794 : 16692641/7465176 :正則 5
2.6457510161477704 : 306335887/115784095 :非正則 7
2.645751633986928 : 2024/765 :正則 7
3.3166108052804133 : 10634068259/3206305739 :非正則 11
3.3166247903554016 : 31521799/9504180 :正則 11
3.605505252249399 : 20241905/5614166 :非正則 13
3.6055514974433893 : 4936/1369 :正則 13
4.122853067245746 : 378927653/91909085 :非正則 17
4.123105625617661 : 41270070401/10009462320 :正則 17
4.3584203886767705 : 998235750499/229036132699 :非正則 19
4.3588989441930615 : 57799/13260 :正則 19
最初は正則連分数と同じくcnt=11回でまわしたのだけど、
非正則連分数の精度が悪いため、cnt2=20と回す数を倍ちかくにしたら、どうにか同じくらいの精度に
なった。
非正則の方がコードにしやすい分、精度が下がってしまったということだ。
分かりやすいさは、精度や速度に比例しないかも
という教訓?