找到给定范围的分母的Pi的最接近的近似值

时间:2017-02-26 16:15:33

标签: algorithm math fractions pi approximation

我正在寻找一种算法(最好在Go或C中),以找出包含范围的可能分母中最接近的公共分数n / d(dmin,dmax,其中1 <= dmin&lt; = dmax&lt; ; = 1e15)。如果有多个与Pi相同距离的公共分数,我想找到分母最小的分数。

注意:强力方法效率不高,因此我正在寻找更智能/更高效的解决方案。

示例:对于dmin = 1且dmax = 10,最接近的公共分数为22/7,与Pi的距离约为0.001

首先想到:观察farey序列,我们可以找到所有分母最接近dmax的近似值。不幸的是,结果不符合dmin的约束。

2 个答案:

答案 0 :(得分:1)

我没有时间获得完整答案,但这是一个部分答案。这种技术使用了连续分数的概念 - 在线有很多关于它们的概念。我会忽略你的值dmin,这在下面没有使用。

根据需要获取continued fraction expansion of pi到任意数量的地方。对于dmax <= 1e15的界限,您只需要前28个数字

[3, 7, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1, 14, 2, 1, 1, 2, 2, 2, 2, 1, 84, 2, 1, 1, 15, 3, 13]

使用短循环查找具有刚刚低于dmax且高于dmax的分母的pi的会聚。在Python中将是

pi_cont_frac = [3, 7, 15, 1, 292, 1, 1, 1, 2, 1, 
                3, 1, 14, 2, 1, 1, 2, 2, 2, 2,
                1, 84, 2, 1, 1, 15, 3, 13]
denomlo, denomhi = 1, 0
numlo, numhi = 0, 1
for q in pi_cont_frac:
    denomlo, denomhi = denomhi, q * denomhi + denomlo
    numlo, numhi = numhi, q * numhi + numlo
    if denomhi > dmax:
        break

某些软件(如Microsoft Excel)会使用分数numlo/denomlo,但可能会有更好的近似值。现在找到使denomhi - r * denomlo低于(或等于)dmax的自然数r的值。

然后,numlo/denomlo(denomhi - r * denomlo)/(denomhi - r * denomlo)是您想要的最接近pi的分数。只需检查哪一个更近。

该算法具有阶数log(dmax),并且由于pi的特性,它通常要低得多。对于dmax <= 1e15,它需要28个循环,但需要更多清理声明。

你可以通过预先计算和存储收敛数(numhi和denomhi的值)并在dmax之上搜索denomhi的值来制作更快的算法。这也只需要28个数字,但分子和分母都需要这个数字。二进制搜索最多需要5个步骤才能找到它 - 几乎是瞬时的。使用更多存储和更少计算的另一种可能性是存储所有中间馏分。存储将达到数百个,至少三百个。如果你不喜欢pi的持续分数扩展的存储列表,你可以使用pi的值来动态计算,但是使用双精度(在C中)只能得到28个数字我告诉你。

如需更多研究,请查看连续分数和中间分数。

答案 1 :(得分:0)

更普遍的问题是如何将分数 p/q 表示为 n/d,其中 d 在所需范围 drange = [dmin, dmax] 中,q 不在 {{ 1}}、drange。我想象了以下问题的细化条件:

  1. gcd(p,q) = 1(因此,对于将 pi 近似为分母在 10 到 15 之间的分数的解决方案,44/14 是不可接受的;(蛮力)答案是 41/13)
  2. gcd(n, d) = 1 这不适用于 pi 的情况,因为分母是任意长的,但它可以适用于找到最接近 30/43 且分母在 160 和 170 之间的分数的情况;蛮力答案是 118/169。
  3. dmin > q 和 Daulton 的方法成功,因为它 a) 偶然地在所需范围内给出 q > dmax 或 b) 如果 d 无条件成功,因为答案 2*(dmin - 1) < dmax 在范围内.

所以困难的情况是

  1. when a/b = m*a/m*b = n/d 但 Daulton 方法要么没有给出满足条件的答案,因为 q > dmax 小于 d
  2. dmin 不适用 Daulton 方法的情况

在任何一种情况下,我们都可以通过考虑分数的 Stern-Brocot table 来理解为什么这很难解决。 Daulton 的方法会将我们带到比预期更小的分母,但答案在表格的更深处,我们无法知道哪条路径会带我们到达我们正在寻找的 q < dmax 的最接近表示。

然而,我们可以做得比检查所有可能性更好。

考虑分数是 p/q。定义一个方法来告诉最接近分数的 n 的倍数:

0 < x < 1

如果我们计算分母为 dmin 和 dmax 的最近分数,我们将看到必须考虑的分子范围。这些将少于分母的数量,因此需要更少的工作。这是使用 SymPy 组合起来的

def nfrac(x, n, reduce=1):
    """Return the multiple of 1/n that is closest to x
    which is not necessarily the fraction closest to x
    with denom less than n;
    """
    from math import gcd
    scaled = int(round(x * n))
    m, r = divmod(scaled, n)
    if reduce:
        g = gcd(r, n)
        r, n = r//g, n//g
    return m*n+r, n

因此,不是考虑与 1700 个不同分母最接近的分数,而是考虑与 [22655, 22896] 范围内的分子(其中 241 个)最接近的分数。这个结果用蛮力方法检查过,发现是一样的。它与从 Daulton 建议的方法中获得的不同:

def inrange(x, a, b):
    w, r = divmod(a, x)
    if not r:
        return True
    if a < (w + 1)*x <= b:
        return True
    return False

def dlimit(f, n, m):
    if f > 1:
        w = int(f)
        a, b, c = dlimit(f - w, n, m)
        return a + b*w, b, c
    p, q = f.numerator, f.denominator
    if q > m:
        approx = f.limit_denominator(m)
        p = approx.numerator
        q = approx.denominator
    if inrange(q, n, m):
        w, r = divmod(n, q)
        if r:
            w += 1
        return w*p, w*q, 'easy'
    N,_ = nfrac(f, n, 0)
    M,_ = nfrac(f, m, 0)
    af = 1/f
    p = [nfrac(af, i, 0) for i in range(N, M + 1)]
    if p[0][0] < n:
        p = p[1:]
    if p[-1][0] > m:
        p = p[:-1]
    assert all(n <= i[0] <= m for i in p)
    return tuple(reversed(sorted([i for i in p if i[0]],
        key=lambda x: abs(f - x[1]/x[0]))[0])) +( (N, M),)

>>> import math
>>> from fractions import Fraction
>>> dlimit(Fraction(math.pi),160000,161700)
(507895, 161668, (22655, 22896))