生成平方根的连续分数

时间:2012-08-29 16:43:32

标签: c++ math square-root

我编写了这段代码,用于生成平方根N的续分数 但是当N = 139时它就会失败 输出应为{11,1,3,1,3,7,1,1,2,11,2,1,1,7,3,1,3,1,22}
虽然我的代码给了我一个394个术语的序列...其中前几个术语是正确的但是当它达到22时它给出12个!

有人可以帮我吗?

vector <int> f;
int B;double A;
A = sqrt(N*1.0);
B = floor(A);
f.push_back(B);                 
while (B != 2 * f[0])) {
    A = 1.0 / (A - B);
    B =floor(A);                            
    f.push_back(B);     
}
f.push_back(B);

6 个答案:

答案 0 :(得分:17)

根本问题是您无法将非正方形的平方根精确表示为浮点数。

如果ξ是精确值,x近似值(假设仍然非常好,特别是floor(ξ) = a = floor(x)仍然保持不变),那么下一个之后的差异连续分数算法的步骤是

ξ' - x' = 1/(ξ - a) - 1/(x - a) = (x - ξ) / ((ξ - a)*(x - a)) ≈ (x - ξ) / (ξ - a)^2

因此,我们看到,在每个步骤中,近似值与实际值之间的差值的绝对值会增加,因为0 < ξ - a < 1。每当出现大的部分商(ξ - a接近0)时,差异就会增大。一旦(绝对值)差值为1或更大,下一个计算出的部分商保证是错误的,但很可能是先出现了第一个错误的部分商。

Charles mentioned使用n正确数字的原始近似值的近似值,可以计算连续分数的n部分商。这是一个很好的经验法则,但正如我们所看到的,任何大的部分商都会花费更多的精确度,从而减少可获得的部分商的数量,有时你会更早地得到错误的部分商。

√139的情况是一个具有相对较长时期且具有几个大的部分商的情况,因此在该期间完成之前出现第一个错误计算的部分商并不奇怪(我很惊讶它不会更早发生。)

使用浮点运算,没有办法阻止它。

但是对于二次方程式的情况,我们可以通过仅使用整数运算来避免这个问题。假设你想计算

的连续分数扩展
ξ = (√D + P) / Q

其中Q除以D - P²D > 1不是完美的正方形(如果不满足可分性条件,则可以将D替换为D*Q²P P*Q Q P = 0, Q = 1 ξ_k = (√D + P_k) / Q_k (with ξ_0 = ξ, P_0 = P, Q_0 = Q) ; a_k ξ_k - a_k = (√D - (a_k*Q_k - P_k)) / Q_k P_{k+1} = a_k*Q_k - P_k。将完整的商写为

ξ_{k+1} = 1/(ξ_k - a_k) = Q_k / (√D - P_{k+1}) = (√D + P_{k+1}) / [(D - P_{k+1}^2) / Q_k],

并表示部分商Q_{k+1} = (D - P_{k+1}^2) / Q_k。然后

P_{k+1}^2 - P_k^2

Q_k

Q_{k+1}

所以Q_{k+1} - 由于D - P_{k+1}^2ξ的倍数,因此归纳ξ是一个整数,(P_k, Q_k)除以Q_k = 1。< / p>

实数k > 0的连续分数展开是周期性的,当且仅当P_k, Q_k是二次方,并且在上述算法中完成周期时,第一对{{1}重复。纯平方根的情况特别简单,句点在R = floor(√D)的第一a_k = floor((R + P_k) / Q_k) 时完成,std::vector<unsigned long> sqrtCF(unsigned long D) { // sqrt(D) may be slightly off for large D. // If large D are expected, a correction for R is needed. unsigned long R = floor(sqrt(D)); std::vector<unsigned long> f; f.push_back(R); if (R*R == D) { // Oops, a square return f; } unsigned long a = R, P = 0, Q = 1; do { P = a*Q - P; Q = (D - P*P)/Q; a = (R + P)/Q; f.push_back(a); }while(Q != 1); return f; } 始终为非负。

使用√7981,部分商可以计算为

{{1}}

因此上述算法的代码变为

{{1}}

,它可以很容易地计算周期长度为182的(例如){{1}}的连续分数。

答案 1 :(得分:3)

罪魁祸首不是floor。罪魁祸首是计算A= 1.0 / (A - B);深入挖掘,罪魁祸首是计算机用来表示实数的IEEE浮点机制。减法和加法会失去精确度。在算法重复执行时反复减法会丢失精度。

当你计算出连续的分数项{11,1,3,1,3,7,1,1,2,11,2}时,你的IEEE浮点值A只有六个位置好而不是人们期望的十五或十六。当你到达{11,1,3,1,3,7,1,1,2,11,2,1,1,7,3,1,3,1}时,你的A值是纯垃圾。它失去了所有精确度。

答案 2 :(得分:1)

数学中的sqrt函数不精确。您可以使用sympy而不是任意高精度。这是一个非常简单的代码,用于计算sympy中包含的任何平方根或数字的连续分数:

from __future__ import division #only needed when working in Python 2.x
import sympy as sp

p=sp.N(sp.sqrt(139), 5000)

n=2000
x=range(n+1)
a=range(n)
x[0]=p

for i in xrange(n):
    a[i] = int(x[i])
    x[i+1]=1/(x[i]-a[i])
    print a[i],

我已将数字的精度设置为5000,然后在此示例代码中计算了2000个连续分数系数。

答案 3 :(得分:1)

如果有人试图解决这个问题,请使用不带整数的语言,这里是适用于JavaScript的接受答案的代码。

注意添加了两个~~(发言人操作员)。

export const squareRootContinuedFraction = D =>{
    let R = ~~Math.sqrt(D);
    let f = [];
    f.push(R);
    if (R*R === D) {
        return f;
    }
    let a = R, P = 0, Q = 1;
    do {
        P = a*Q - P;
        Q = ~~((D - P *P)/Q);
        a = ~~((R + P)/Q);
        f.push(a);
    } while (Q != 1);
    return f;
};

答案 4 :(得分:0)

我在电子表格中使用了算法,我也得到了12,我认为你的算法一定是犯了错误,我尝试了253个值,B没有达到它的最终值。

你能不能再解释算法应该做些什么以及如何运作?

我想我得到了你的算法而你在你的问题中犯了一个错误,它应该是12.为了将来的参考,算法可以在这个页面找到http://en.wikipedia.org/wiki/Continued_fraction并且很容易出现十进制问题数值计算问题,如果反向值非常接近你想要舍入的整数。

在Excel下进行原型时,我无法重现3.245维基页面的示例,因为在某些时候,Floor()将数字放到3而不是4,因此需要进行一些边界检查以检查准确性。 ..

在这种情况下,您可能希望添加最大迭代次数,检查退出条件的容差(退出条件应该是A等于B btw)

答案 5 :(得分:0)

我使用 Surd Storage 类型来实现 n 的平方根的无限精度。

(b * \sqrt(n) + d)/c

=

(b * c * sqrt(n) - c * d + a_i * c^2) / (b^2 * n - d^2 - (a_i * c)^2 + 2* a_i * c * d )

sqrt(n) 的下限值只使用一次。之后剩余的迭代存储为 surd 类型。这避免了在其他算法中看到的舍入误差,并且可以实现无限(内存受限)分辨率。

a_0 = sqrt (n) 的下限

a_i = (b_i * a_0 + d_i) / c_i

b_i+1 = b_i * c

c_i+1 = (b_i)^2 * n - (d_i)^2 - (a_i * c_i)^2 + 2 * a_i * c_i * d_i

d_i+1 = a_i * (c_i)^2 - c_i * d_i

g = gcd(b_i+1, c_i+1, d_i+1)

b_i+1 = b_i+1 / g

c_i+1 = c_i+1 / g

d_i+1 = d_i+1 / g

a_i+1 = (b_i+1 * x + d_i+1) / c_i+1

然后对于 i=0 到 i=Maximum_terms 产生连分数 以 [a_0;a_1,a_2 ... ,2*a_0] 开头

当 a_i 项等于 a_0 的 2 倍时,我终止分数。 这是序列重复的点。

数学是由 Electro World 完成的,还有一个非常好的视频 数学可以在这里找到https://youtu.be/GFJsU9QsytM

下面提供了使用 BigInteger 用 Ja​​va 编写的源代码。 希望你喜欢。

如果找到重复序列,则返回布尔值 true,如果找到重复序列,则返回 false 未找到所需精度的重复序列。

可以根据 Maximum_terms 轻松修改精度以适应。

平方根 139 [11;1,3,1,3,7,1,1,2,11,2,1,1,7,3,1,3,1,22] 重复长度 18

15 [3;1,6] 重复长度 2 的平方根

2501 [50;100] 重复长度 1 的平方根

10807的平方根 [103;1,22,9,2,2,5,4,1,1,1,6,15,1,5,2,1,3,6,34,2,34,6,3,1 ,2,5,1,15,6,1,1,1,4,5,2,2,9,22,1,206] 重复长度 40

可能的两倍加速是查看系列的回文性质。 在本例中为 34、2、34。 只需要确定一半的序列。

    public static Boolean SquareRootConFrac(BigInteger N) {
BigInteger A,B=BigInteger.ONE,C=B,D=BigInteger.ZERO;
BigInteger A0=N.sqrt(),Bi=B,Ci=C,Di=D,G;
BigInteger TwoA0 = BigInteger.TWO.multiply(A0);
int Frac_Length=0, Maximum_terms=10000; //Precision 10000 terms
String str="";
Boolean Repeat=false, Success=false, Initial_BCD=true;

while(!Repeat) {
    Frac_Length++;                         Success=!(Frac_Length==Maximum_terms);
    A=((B.multiply(A0)).add(D)).divide(C); Repeat=A.equals(TwoA0)||!Success;

    Bi=B.multiply(C);
    Ci=(B.multiply(B).multiply(N)).subtract(D.multiply(D)).subtract(A.multiply(A).multiply(C).multiply(C)).add(BigInteger.TWO.multiply(A).multiply(C).multiply(D));
    Di=(A.multiply(C).multiply(C)).subtract(C.multiply(D));
    G=Bi.gcd(Ci).gcd(Di);
    B=Bi.divide(G);C=Ci.divide(G);D=Di.divide(G);
    
    if(Initial_BCD) {str="["+A+";";System.out.print(str);Initial_BCD=false;}
    else            {str=""+A;System.out.print(str);if(!Repeat){str=",";System.out.print(str);}}
}
str="]";System.out.println(str);
str="repeat length ";System.out.print(str);
if(Success) {str=""+(Frac_Length-1);System.out.println(str);}
else        {str="not found";System.out.println(str);}
return Success;
}