连分数及其程序实现
在数学中,将具有以下形式的分数称为连分数。所有分子都为 1,所以又称为正规连分数。当分子可以是其他值时,则属于广义连分数。对于正规的连分数,可以使用 \([a_0, a_1, ..., a_n]\) 这样的数组来表示。
连分数及其求解
对于有理数,可以表示为有限的连分数。对于无理数,自然没有办法使用有限的连分数来精确表示,而只能去近似表示。例如:
对应的数组是:[1, 2, 2, 2, ...]
,2 会重复出现。根 3 对应的数组是 [1, 1, 2, 1, 2, ...]
,1 和 2 会重复出现。这种形式的连分数属于循环连分数,只有二次无理数可以表示为这种形式。
基于连分数的形式,可以自然地使用递归来求解。下面是一种计算方法。cf
的参数 f
是一个函数,用来计算连分数数组。count
用来限制数组的大小。fgen
是一个生成器函数,用来生成数组的每一项元素。当生成器结束时,使用 float('inf')
来截断连分数。
def cf(f, count):
def _cf(gen):
try:
return next(gen) + 1 / _cf(gen)
except StopIteration:
return float('inf')
gen = fgen(f, count)
return _cf(gen)
def fgen(f, count):
i = 0
while i <= count:
yield f(i)
i += 1
使用程序计算几个连分数:
# 1.2345
>>> cf(lambda x: [1, 4, 3, 1, 3, 1, 1, 2, 5][x], 8)
1.235
# √2
>>> cf(lambda x: 1 if x == 0 else 2, 10)
1.41421355164605467
# √3
>>> cf(lambda x: 1 if x == 0 or x % 2 == 1 else 2, 10)
1.7320490367775832
对于数学常数 \(e\),因其不是二次无理数,连分数不属于循环连分数。但它的连分数也呈现一定的规律:
下面是对常数 \(e\) 的计算:
def fe(x):
if x == 0:
return 2
if x % 3 == 2:
return (x + 1) // 3 * 2
else:
return 1
>>> cf(fe, 10)
2.7182817182817183
另一个常数圆周率 \(\pi\) 的连分数却不具备规律:
有理数转化成连分数
计算一个有理数的连分数,可以采用辗转相除的方式。函数f2cf
可以接受 float
或者形如 'xx.xxx'
, 'xx/xxx'
的字符串形式。内部都会转化成分子分母为整数的形式进行处理,这样可以避免处理浮点数带来的误差问题。
def f2cf(val):
if isinstance(val, float):
val = str(val)
x, y = re.compile(r'[/, .]').split(val)
if '.' in val:
tmp = 10**len(y)
x = int(x) * tmp + int(y)
y = tmp
else:
x, y = int(x), int(y)
arr = []
gen = gcd(x, y)
for item in gen:
arr.append(item)
return arr
def gcd(x, y):
while y:
yield x // y
x, y = y, x % y
几个计算示例:
>>> import math
# 有理数
>>> f2cf(1.333)
[1, 3, 333]
# √3 和 π 是无理数,由于是精度有限,仍可以计算
# √3,由于是近似形式,1,2 重复出现的规律会被打破
>>> f2cf(math.sqrt(3))
[1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1, 40, 1, 2, 2, 1, 13, 1, 3, 2, 1, 1, 5, 1, 4, 3, 2]
# π
>>> f2cf(math.pi)
[3, 7, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1, 14, 4, 2, 3, 1, 12, 5, 1, 5, 20, 1, 11, 1, 1, 1, 2]
广义连分数
上面提到 \(\pi\) 无法的连分数不具有规律,但它可以表示成具有规律的广义连分数形式:
扩展函数 cf
,以支持这种分子不是定值的情况:
def cf_general(f1, f2, count):
def _cf(gen1, gen2):
try:
num1, num2 = next(gen1), next(gen2)
return num1 + num2 / _cf(gen1, gen2)
except StopIteration:
return float('inf')
gen1, gen2 = fgen(f1, count), fgen(f2, count)
return _cf(gen1, gen2)
对上图中最后一种广义连分数进行测试:
>>> cf_general(lambda x: 0 if x == 0 else 2*x-1, lambda x: 4 if x == 0 else x**2, 20)
3.141592653589791