Chudnovsky二元分裂和分解

时间:2015-11-09 21:21:08

标签: c recursion gmp pi factoring

this article中,给出了使用二进制分裂的Chudnovsky pi公式的快速递归公式。在python中:

C = 640320
C3_OVER_24 = C**3 // 24
def bs(a, b):
    if b - a == 1:
        if a == 0:
            Pab = Qab = 1
        else:
            Pab = (6*a-5)*(2*a-1)*(6*a-1)
            Qab = a*a*a*C3_OVER_24
        Tab = Pab * (13591409 + 545140134*a) # a(a) * p(a)
        if a & 1:
            Tab = -Tab
    else:
        m = (a + b) // 2
        Pam, Qam, Tam = bs(a, m)
        Pmb, Qmb, Tmb = bs(m, b)

        Pab = Pam * Pmb
        Qab = Qam * Qmb
        Tab = Qmb * Tam + Pam * Tmb
    return Pab, Qab, Tab

N = int(digits/DIGITS_PER_TERM + 1)
# Calclate P(0,N) and Q(0,N)
P, Q, T = bs(0, N)
one = 10**digits
sqrtC = sqrt(10005*one, one)
return (Q*426880*sqrtC) // T

这种方法已经非常快了,但是它提到GMP库网站gmp-chudnovsky.c上的实现也会影响二进制拆分中的分子和分母。由于代码已经过优化且难以理解,因此如何完成这一操作背后的一般概念是什么?我无法判断分数是否被简化,数字是分解形式而不是完全相乘,或两者兼而有之。

以下是二进制拆分的代码示例:

/* binary splitting */
void
bs(unsigned long a, unsigned long b, unsigned gflag, long int level)
{
  unsigned long i, mid;
  int ccc;

  if (b-a==1) {
    /*
      g(b-1,b) = (6b-5)(2b-1)(6b-1)
      p(b-1,b) = b^3 * C^3 / 24
      q(b-1,b) = (-1)^b*g(b-1,b)*(A+Bb).
    */
    mpz_set_ui(p1, b);
    mpz_mul_ui(p1, p1, b);
    mpz_mul_ui(p1, p1, b);
    mpz_mul_ui(p1, p1, (C/24)*(C/24));
    mpz_mul_ui(p1, p1, C*24);

    mpz_set_ui(g1, 2*b-1);
    mpz_mul_ui(g1, g1, 6*b-1);
    mpz_mul_ui(g1, g1, 6*b-5);

    mpz_set_ui(q1, b);
    mpz_mul_ui(q1, q1, B);
    mpz_add_ui(q1, q1, A);
    mpz_mul   (q1, q1, g1);
    if (b%2)
      mpz_neg(q1, q1);

    i=b;
    while ((i&1)==0) i>>=1;
    fac_set_bp(fp1, i, 3);  /*  b^3 */
    fac_mul_bp(fp1, 3*5*23*29, 3);
    fp1[0].pow[0]--;

    fac_set_bp(fg1, 2*b-1, 1);  /* 2b-1 */
    fac_mul_bp(fg1, 6*b-1, 1);  /* 6b-1 */
    fac_mul_bp(fg1, 6*b-5, 1);  /* 6b-5 */

    if (b>(int)(progress)) {
      printf("."); fflush(stdout);
      progress += percent*2;
    }

  } else {
    /*
      p(a,b) = p(a,m) * p(m,b)
      g(a,b) = g(a,m) * g(m,b)
      q(a,b) = q(a,m) * p(m,b) + q(m,b) * g(a,m)
    */
    mid = a+(b-a)*0.5224;     /* tuning parameter */
    bs(a, mid, 1, level+1);

    top++;
    bs(mid, b, gflag, level+1);
    top--;

    if (level == 0)
      puts ("");

    ccc = level == 0;

    if (ccc) CHECK_MEMUSAGE;

    if (level>=4) {           /* tuning parameter */
#if 0
      long t = cputime();
#endif
      fac_remove_gcd(p2, fp2, g1, fg1);
#if 0
      gcd_time += cputime()-t;
#endif
    }

    if (ccc) CHECK_MEMUSAGE;
    mpz_mul(p1, p1, p2);

    if (ccc) CHECK_MEMUSAGE;
    mpz_mul(q1, q1, p2);

    if (ccc) CHECK_MEMUSAGE;
    mpz_mul(q2, q2, g1);

    if (ccc) CHECK_MEMUSAGE;
    mpz_add(q1, q1, q2);

    if (ccc) CHECK_MEMUSAGE;
    fac_mul(fp1, fp2);

    if (gflag) {
      mpz_mul(g1, g1, g2);
      fac_mul(fg1, fg2);
    }
  }

  if (out&2) {
    printf("p(%ld,%ld)=",a,b); fac_show(fp1);
    if (gflag)
      printf("g(%ld,%ld)=",a,b); fac_show(fg1);
  }
}

2 个答案:

答案 0 :(得分:6)

以下评论是关键:

/*
    g(b-1,b) = (6b-5)(2b-1)(6b-1)
    p(b-1,b) = b^3 * C^3 / 24
    q(b-1,b) = (-1)^b*g(b-1,b)*(A+Bb).
*/
/*
    p(a,b) = p(a,m) * p(m,b)
    g(a,b) = g(a,m) * g(m,b)
    q(a,b) = q(a,m) * p(m,b) + q(m,b) * g(a,m)
*/
/*
          p*(C/D)*sqrt(C)
    pi = -----------------
             (q+A*p)
*/

观察:

  1. 如果我们在最终分部中将pq缩放k,则不会影响结果。
  2. 在计算与第二组评论相对应的合并操作时,如果我们按p(a,m)缩放g(a,m)q(a,m)k中的每一个,那么因素只会传到p(a,b)g(a,b)q(a,b);同样,如果我们要扩展p(m,b)g(m,b)q(m,b)中的每一个,那么该因素就可以继续。
  3. 该行

    fac_remove_gcd(p2, fp2, g1, fg1);
    

    实际上是

    k = gcd(p2, g1);
    p2 /= k; // p(m,b) /= k
    g1 /= k; // g(a,m) /= k
    

    这具有由p(a,b)缩减g(a,b)q(a,b)gcd的净效果。通过前两个观察结果,这种降尺度一直干净地贯穿到最终结果。

  4. 后记

    我已经尝试了三种在Python中实现分解的方法。

    • 使用不可变列表跟踪因子分解会让事情变得非常糟糕,因为维护列表的工作量太大了。
    • 使用gmpy的gcd不会加速
    • 预先计算最多6 * N的素数列表(即可能除以g)并测试每个质数,这会使事情减慢2到3倍。

    我的结论是,使用这种方法加速需要使用可变状态来跟踪因子分解,因此它对可维护性来说是一个很大的打击。

答案 1 :(得分:3)

我没有查看完整的代码,但我快速浏览了一下,以便更好地理解您在问题中提供的摘录。

为了回答你问题中的一些观点,首先看一下这段代码:

typedef struct {
  unsigned long max_facs;
  unsigned long num_facs;
  unsigned long *fac;
  unsigned long *pow;
} fac_t[1];

它将一种新类型定义为一种结构(如果你根本不了解C,那就说它就像一个基本的Pyhton对象嵌入变量而没有方法)。此类型允许将整数作为两个整数值和两个数组(例如:两个列表)处理:

  • 最大因素
  • 因素数
  • 因素列表
  • 这些因素的(相应)权力清单

同时代码将相同的数字保留为libgmp类型的大整数(这是mpz_t pmpz_t g在参数中的含义。功能)。

现在,您正在展示的功能如何?它被称为fac_remove_gcd;最初的fac与前面描述的类型的名称有关;以下两个单词很容易理解:通过gcd 划分两个fac类型的整数。

C代码迭代两个列表中的两个因子列表;由于因子是有序的,所以很容易同步两个列表(围绕else ifelse语句的代码部分);只要检测到两个共同因素(初始if陈述),划分就是一个简单的减法问题:在这个因子的两个幂列表中减去最小的幂(例如a = 2 * 5 ^ 3 * 17并且b = 3 * 5 ^ 5 * 19,值3将在 a b 的幂列表中被减去,该列表对应于因子5,导致a = 2 * 5 ^ 0 * 17和b = 3 * 5 ^ 2 * 19)。

在此操作期间,会创建一个(同一fac类型的)数字并调用fmul;这显然是两个数字的gcd。

在此步骤之后,名为fmul且属于fac类型的gcd将转换为GMP大整数,其中包含名为bs_mul的函数(也在程序代码中)。这允许将GCD计算为大整数,以便以两种形式同步分割数的新值:大整数和特殊fac类型。一旦GCD被计算为一个大整数,就可以很容易地将两个初始数除以GCD。

因此,这些函数会对每个初始数字的两个版本起作用。

希望它可以提供帮助。