【问题标题】:Generating continued fractions for square roots生成平方根的连分数
【发布时间】:2012-08-29 16:43:32
【问题描述】:

我编写了这段代码来生成平方根 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);

【问题讨论】:

  • A、B的种类有哪些?
  • 我在这里的回答可能会有所帮助。它生成并打印出任意double 的连分数。
  • 您遇到的根本问题只是数值不稳定的一个糟糕案例。生成连分数本质上是数值不稳定的。我链接到的那个答案中的代码可以解决问题。
  • 糟糕,我意识到我实际上并没有链接到我的答案。 It's here.
  • 我的答案中的代码为3.245 吐出{3, 4, 12, 3, 1}。所以看起来它工作正常。

标签: c++ math square-root


【解决方案1】:

根本问题是您不能将非平方的平方根精确地表示为浮点数。

如果ξ 是精确值而x 是近似值(这应该还是相当不错的,因此特别是floor(ξ) = a = floor(x) 仍然成立),那么在连分数算法的下一步之后的差异是

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

因此我们看到,在每一步中,近似值和实际值之间的差值的绝对值都会增加,因为0 &lt; ξ - a &lt; 1。每次出现较大的偏商时(ξ - a 接近于 0),差值就会增加一个很大的倍数。一旦差值(绝对值)为 1 或更大,则保证下一个计算的部分商是错误的,但很可能第一个错误的部分商发生得更早。

Charles mentioned 使用具有正确数字的原始近似值 n 的近似值,您可以计算大约 n 连分数的部分商。这是一个很好的经验法则,但正如我们所见,任何大的部分商都需要更高的精度,因此会减少可获得的部分商的数量,而且有时你会更早地得到错误的部分商。

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

使用浮点运算,没有办法防止这种情况发生。

但是对于二次曲线的情况,我们可以通过仅使用整数算术来避免这个问题。假设您要计算的连分数展开式

ξ = (√D + P) / Q

其中Q除以D - P²D &gt; 1不是完全平方(如果不满足可除性条件,可以将D换成D*Q²P换成P*Q和@987654340 @ 和;你的情况是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^2Q_k 的倍数,所以通过归纳Q_{k+1} 是一个整数,Q_{k+1} 除以D - P_{k+1}^2

实数ξ 的连分数展开是周期性的当且仅当ξ 是二次方程,并且当在上述算法中第一对(P_k, Q_k) 重复时,周期完成。纯平方根的情况特别简单,当第一个Q_k = 1为一个k &gt; 0时,句号就完成了,P_k, Q_k总是非负数。

使用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 的连分数,周期长度为 182。

【讨论】:

  • 非常感谢。这是一个非常大的帮助。但是你能不能给我一个论文链接或者一些描述 CF 算法的东西并详细解释
  • 每本书都或多或少地描述了它们(深度不同),其标题与“数论导论”非常相似。根据我自己的经验,我可以推荐经典的 Hardy/Wright,Hua Loo Keng 的书,以及 - 需要注意的是 - Don Redmond 的书。 (需要注意的是,至少在我阅读的版本中,Redmond 的书有很多排版错误,例如 13·27 显示为 1327,10 平方为 102 等,这经常令人恼火。除此之外,这是一个非常好的书,并且比其他两本更广泛地对待 CF。)
  • 很好的解决方案。你能解释一下为什么 a = (R + P)/Q 吗?我不太明白。
  • @ArulxZ 部分商的值为floor((sqrt(D) + P)/Q)。现在事实是,对于一个正整数Q,任何正实数x 都有floor(x/Q) == floor(floor(x)/Q),所以我们有floor((sqrt(D)+P)/Q) == floor(floor(sqrt(D)+P)/Q),但是floor(sqrt(D)+P) == floor(sqrt(D)) + P 因为P 是一个整数,而R == floor(sqrt(D)) ,变成floor((R+P)/Q)。由于 C++ 中的整数除法会截断,并且一切都是正数,因此 C++ 代码中的表达式 (R+P)/Q 会根据需要计算 floor((R+P)/Q)
  • 谢谢。你太棒了:)
【解决方案2】:

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

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

【讨论】:

  • 可以从具有 n 位精度的数字中提取关于 n 连分数项的良好初步近似值(仅在以 10 为底的巧合)。正如您所指出的,如果您尝试服用更多,您会得到垃圾。
【解决方案3】:

数学中的 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 个连分数系数。

【讨论】:

  • 是的,该算法适用于一般情况,但正如 Daniel Fischer 的回答所示,无需借助任意精度算术即可确定二次数的精确周期性连分数。当然,如果您想将该连分数评估为高精度,那么您确实需要比标准双精度更好的东西。顺便说一句,将 Python 代码发布到标记为 C++ 的问题上可能没有多大意义,OTOH 您的代码不使用任何 Python“技巧”,因此 应该 对阅读此页面的任何人来说都相当清楚。
【解决方案4】:

如果有人试图用一种没有整数的语言来解决这个问题,这里是适用于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;
};

【讨论】:

    【解决方案5】:

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

    你能多解释一下算法应该做什么以及它是如何工作的吗?

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

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

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

    【讨论】:

      【解决方案6】:

      您的代码没有计算n 的平方根。它尝试计算已计算的√n 的连分数。我的意思是没关系,但是,如果它是正确的,你的方法更适合一般小数到有理数的转换。但是,对于常规(简单)连分数(所有分子都是 1)的 sqrt 函数,算法略有不同。

      但是问题还没有结束。是的,通常情况下,√n 的 CF 系数采用重复回文的形式,以第一个非零系数的双倍结尾。如√31 =[5;1,1,3,5,3,1,1,10,1,1,3,5,3,1,1,10..]。现在没有简单的方法来查询每个给定n 的回文长度。有一些已知的模式,但它们远未定义所有n 的通用模式。所以在第一个回文结束时停止迭代是一种非常不确定的方法。想象一下

                __
      √226 =[15;30]
      

      同时

                ____________________________________________________
      √244 =[15;1,1,1,1,1,2,1,5,1,1,9,1,6,1,9,1,1,5,1,2,1,1,1,1,1,30]
      

      如果您决定在大多数情况下在 2*f[0] 处停止迭代,您会得到一个像 √226 这样的错误近似值,或者像 √244 这样的一个过度计算的近似值。此外,一旦n 增长,追逐回文的终点就变得毫无意义,因为你永远不需要这样的精确度。

                  ___________________________________________________________________________________________________________________________________________________________________________
      √7114 = [84;2,1,9,3,1,10,2,23,1,1,1,1,1,2,1,27,2,1,1,3,1,2,1,1,1,16,4,3,1,3,2,1,6,18,1,1,2,6,11,11,6,2,1,1,18,6,1,2,3,1,3,4,16,1,1,1,2,1,3,1,1,2,27,1,2,1,1,1,1,1,23,2,10,1,3,9,1,2,168]
      

      在这种情况下,一旦获得必要的精度就停止迭代是合理的。正如我在开头提到的,有两种方法。

      1. 通用的 Decimal to Rational 算法从任何十进制数中获取简单的连分数。这将使 CF 精确解析为该小数,而不会出现任何浮点错误。有关此算法的详细说明,您可以查看a previous answer of mine
      2. √n 恰好有一个更直接的算法,它与 1 基本相同,但针对平方根进行了调整。在这种情况下,您不提供√n,而是提供n

      思路如下。我们必须为在分子处包含平方根值的输入定义一个通用形式。然后我们尝试在连分数部分达到相同的表达式以便能够迭代。

      让我们的输入格式为

      q + √n
      ______
         p
      

      对于简单的平方根运算,我们可以假设q0 并且p1。如果我们可以在下一阶段建立这种形式,那么我们可以很容易地进行迭代。

      q = 0p = 1m√n的整数部分和1/x是浮动部分的初始阶段开始,我们的目标是将x变成(q + √n) / p形式;

                1            1             1       (√n + m)        √n + m
      √n = m + ___ ⇒ x = _______ ⇒ x = ________ . ________ ⇒ x = ________
                x         √n - m        (√n - m)   (√n + m)        n - m^2
      

      现在√n 是分子,我们的形式是;

          √n + q
      x = ______
             p
      

      q = mp = n - m^2。在这一点上,您可以计算x 和下一个m 通过地板x。算法的广义形式变为;

          √n + q        1                p          p(√n - (q - pm))   p(√n + (pm - q))
      x = ______ = m + ___ ⇒ x' = ______________ = _________________ = ________________
             p          x'         √n + (q - pm)     n - (q - pm)^2     n - (q - pm)^2
      

      此时p 可以被n - (q - pm)^2 整除。现在这是稳定的,我们可以根据需要扩展它。让我们为qp 分配新的任务;

      q' = pm-q;
      p' = (n - q'^2)/p;
      
           √n + q'
      x' = ______
              p'
      
      m' = Math.floor(x')
      

      请注意,当p' 变为 1 (n - q'^2 = p) 时,我们处于回文的末尾。然而,为了决定在哪里停止,我使用了与我的 toRational 算法中描述的机制相同的机制,正如上面备选方案 1 中所链接的那样。一旦达到 JS 浮点分辨率,它基本上就会停止。 JavaScript 代码如下;

      function rationalSqrt(n){
        var nr = Math.sqrt(n),
            m  = Math.floor(nr),
            p  = n-m**2,
            q  = m,
            cs = [m],
            n0 = 1,
            d0 = 0,
            n1 = m,
            d1 = 1,
            n2 = 0,
            d2 = 1;
        if (nr === m) return {n:m,d:1,cs};
        while (Math.abs(nr-n2/d2) > Number.EPSILON){
          m = Math.floor((nr+q)/p);
          q = m*p-q;
          p = (n-q**2)/p;
          cs.push(m);
          n2 = m*n1+n0;
          d2 = m*d1+d0;
          n0 = n1;
          d0 = d1;
          n1 = n2;
          d1 = d2;
        }
        return {n:n2,d:d2,cs};
      }
      

      这两种算法略有不同。

      1. ~60% 的时间它们产生相同的分数。
      2. ~27% 的时间toRational 在 JS 浮点分辨率内给出更小的分子和分母。
      3. ~13% 的时间rationalSqrt(这个)在 JS 浮点分辨率内给出更小的分子和分母。
      4. rationalSqrt 将得到精确的系数,就像人们期望从平方根得到的一样,但一旦分辨率足够,就会被截断。
      5. toRational 给出了预期的 couse 系数,但最后一个可能与您对平方根系列的预期完全无关。

      一个这样的例子是;

      rationalSqrt(511); //returns
      { n : 882184734
      , d : 39025555
      , cs: [22,1,1,1,1,6,1,14,4,1,21,1,4,14,1,6,1]
      }
      

      同时

      toRational(Math.sqrt(511));
      { n : 1215746799
      , d : 53781472
      , cs: [22,1,1,1,1,6,1,14,4,1,21,1,4,14,1,10]
      }
      

      进一步思考: 考虑给定 RCF 系数。我们可以将rationalSqrt 的算法倒转得到(q + √n) / p 的形式吗?这可能是一项有趣的任务。

      【讨论】:

        【解决方案7】:

        我使用 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

        下面提供了用 Java 编写的 BigInteger 源代码。 希望你喜欢。

        如果找到重复序列,则返回布尔值 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;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-04
          • 2011-03-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多