找到2 ^(1/3)的连续分数到非常高的精度

时间:2016-12-17 17:00:13

标签: algorithm math precision rational-number continued-fractions

我在这里使用符号

enter image description here

可以通过计算然后应用定义来找到数字的连续分数,但这需要至少O(n)位的内存来找到 0 1 ... a n ,实际上情况要糟糕得多。使用双浮点精度,只能找到 0 1 ... a 19

另一种方法是使用以下事实:如果a,b,c是有理数,则存在唯一有理数p,q,r,使得1 /(a + b * 2 1/3 + c * 2 2/3 )= x + y * 2 1/3 + z * 2 2/3 ,即

enter image description here

因此,如果我使用boost rational lib将x,y和z表示为绝对精度,我可以获得floor(x + y * 2 1/3 + z * 2 2 / 3 )准确地仅对2 1/3 和2 2/3 使用双精度,因为我只需要它在真值的1/2之内。不幸的是,x,y和z的分子和分母增长得相当快,如果你使用常规浮点数而错误堆积很快。

这样我就可以在一小时内计算出一个 0 ,一个 1 ...一个 10000 ,但不知何故mathematica可以在2秒内完成。这是我的参考代码

#include <iostream>

#include <boost/multiprecision/cpp_int.hpp>
namespace mp = boost::multiprecision;

int main()
{
    const double t_1 = 1.259921049894873164767210607278228350570251;
    const double t_2 = 1.587401051968199474751705639272308260391493;
    mp::cpp_rational p = 0;
    mp::cpp_rational q = 1;
    mp::cpp_rational r = 0;
    for(unsigned int i = 1; i != 10001; ++i) {
        double p_f = static_cast<double>(p);
        double q_f = static_cast<double>(q);
        double r_f = static_cast<double>(r);
        uint64_t floor = p_f + t_1 * q_f + t_2 * r_f;
        std::cout << floor << ", ";
        p -= floor;
        //std::cout << floor << " " << p << " " << q << " " << r << std::endl;
        mp::cpp_rational den = (p * p * p + 2 * q * q * q +
                                4 * r * r * r - 6 * p * q * r);
        mp::cpp_rational a = (p * p - 2 * q * r) / den;
        mp::cpp_rational b = (2 * r * r - p * q) / den;
        mp::cpp_rational c = (q * q - p * r)     / den;
        p = a;
        q = b;
        r = c;
    }
    return 0;
}

2 个答案:

答案 0 :(得分:2)

拉格朗日算法

该算法在例如Knuth的书“计算机程序设计的艺术”第2卷(第4.5节“欧几里德算法分析中的第13章,第3版第375页”)中进行了描述。 / p>

f为整数系数的多项式,其唯一实根是无理数x0 > 1。然后拉格朗日算法计算x0的连续分数的连续商。

我在python中实现了它

def cf(a, N=10):
    """
    a : list - coefficients of the polynomial,
        i.e. f(x) = a[0] + a[1]*x + ... + a[n]*x^n
    N : number of quotients to output
    """
    # Degree of the polynomial
    n = len(a) - 1

    # List of consecutive quotients
    ans = []

    def shift_poly():
        """
        Replaces plynomial f(x) with f(x+1) (shifts its graph to the left).
        """
        for k in range(n):
            for j in range(n - 1, k - 1, -1):
                a[j] += a[j+1]

    for _ in range(N):
        quotient = 1
        shift_poly()

        # While the root is >1 shift it left
        while sum(a) < 0:
            quotient += 1
            shift_poly()
        # Otherwise, we have the next quotient
        ans.append(quotient)

        # Replace polynomial f(x) with -x^n * f(1/x)
        a.reverse()
        a = [-x for x in a]

    return ans

我的计算机上运行cf([-2, 0, 0, 1], 10000)大约需要1秒。 (系数对应于多项式x^3 - 2,其唯一实根为2 ^(1/3)。)输出与Wolfram Alpha中的输出一致。

<强>买者

在函数内部计算的多项式的系数很快变为非常大的整数。所以这种方法需要在其他语言中实现一些bigint(纯python3处理它,但是例如numpy不会。)

答案 1 :(得分:1)

你可能有更多的运气计算2 ^(1/3)到高精度,然后尝试从中导出连续分数,使用区间运算来确定精度是否足够。

这是我在Python中使用Halley迭代来计算固定点的2 ^(1/3)。死代码试图通过牛顿迭代比Python更有效地计算定点倒数 - 没有骰子。

从我的机器开始的时间大约是30秒,主要用于从固定点表示中提取连续分数。

prec = 40000
a = 1 << (3 * prec + 1)
two_a = a << 1
x = 5 << (prec - 2)
while True:
    x_cubed = x * x * x
    two_x_cubed = x_cubed << 1
    x_prime = x * (x_cubed + two_a) // (two_x_cubed + a)
    if -1 <= x_prime - x <= 1: break
    x = x_prime

cf = []
four_to_the_prec = 1 << (2 * prec)
for i in range(10000):
    q = x >> prec
    r = x - (q << prec)
    cf.append(q)
    if True:
        x = four_to_the_prec // r
    else:
        x = 1 << (2 * prec - r.bit_length())
        while True:
            delta_x = (x * ((four_to_the_prec - r * x) >> prec)) >> prec
            if not delta_x: break
            x += delta_x
print(cf)