Karatsuba 算法的效率

Posted

技术标签:

【中文标题】Karatsuba 算法的效率【英文标题】:Efficiency of Karatsuba algorithm 【发布时间】:2015-08-24 18:28:28 【问题描述】:

我正在尝试构建自己的 RSA 密码版本。 问题之一是有一种快速有效的方法来将两个数字相乘。我不明白BigInteger 代码足以自己计算其方法multiply() 的时间复杂度,而评论说复杂度是 O(n²) 。我决定找到 Karatsuba 算法的代码并进行测试。 结果……很奇怪。

几乎每次,普通乘法算法的效果都比 Karatsuba 好,无论这两个数字的位数或变量 limitOfBitsForKaratsubaEfficiency(即两个数字必须具有的位数) Karatsuba 更有效...理论上)。

现在,我研究了算法和实际实现:Karatsuba 应该在理论上和实践上都获胜。有人知道为什么测试偏爱通用乘法算法吗?


我使用的代码如下。我只调整了两行代码:limitOfBitsForKaratsubaEfficiencyint N = Integer.parseInt("100000");

public static BigInteger karatsuba(BigInteger x, BigInteger y) 

    // cutoff to brute force
    int N = Math.max(x.bitLength(), y.bitLength());
    if (N <= limitOfBitsForKaratsubaEfficiency)     return x.multiply(y);                // optimize this parameter

    // number of bits divided by 2, rounded up
    N = (N / 2) + (N % 2);

    // x = a + 2^N b,   y = c + 2^N d
    BigInteger x1 = x.shiftRight(N);
    BigInteger x0 = x.subtract(x1.shiftLeft(N));
    BigInteger y1 = y.shiftRight(N);
    BigInteger y0 = y.subtract(y1.shiftLeft(N));

    // compute sub-expressions
    BigInteger ac    = karatsuba(x0, y0);
    BigInteger bd    = karatsuba(x1, y1);
    BigInteger abcd  = karatsuba(x0.add(x1), y0.add(y1));

    return ac.add(abcd.subtract(ac).subtract(bd).shiftLeft(N)).add(bd.shiftLeft(2*N));



public static void main(String[] args) 
    long start, stopKara, stopNorma;
    Random random = new Random();
    int N = Integer.parseInt("100000");
    BigInteger a,b,c,d;

    for(int i=0 ; i<15 ; i++)
        
        a = new BigInteger(N, random);
        b = new BigInteger(N, random);
        System.out.printf("The numbers to be multiplied are: \n\t %s \n\t %s\n", a.toString(), b.toString());

        start = System.nanoTime(); 
        c = karatsuba(a, b);
        stopKara = System.nanoTime();
        stopKara = stopKara - start;
        System.out.printf("The karatsuba algorithm has computed %d milliseconds.\n", stopKara);

        start = System.nanoTime(); 
        d = a.multiply(b);
        stopNorma = System.nanoTime();
        stopNorma = stopNorma - start;
        System.out.printf("The common multiplication algorithm has computed %d milliseconds.\n", stopNorma);

        if(c.equals(d)) System.out.println("The two products are equals.");
        else                System.out.println("The two products are NOT equals: the karatsuba method does not works!");
        System.out.printf("The difference Time(Karatsuba)-Time(normalAlgorithm) is: \t %d", stopKara - stopNorma);

        System.out.printf("\n\n\n");
        

    static BigInteger TWO = BigInteger.valueOf(2);
    static BigInteger ONE = BigInteger.ONE;
    static int limitOfBitsForKaratsubaEfficiency = 640;

编辑:我找到了BigInteger.multiply() 使用的两种方法。我绝对不是位对位操作方面的专家,但是这两个for-cicles 让我觉得复杂度是 O(n²)。 Karatsuba 应该是 O(n 1.585 )。

  /** Multiply x[0:len-1] by y, and write the len least
   * significant words of the product to dest[0:len-1].
   * Return the most significant word of the product.
   * All values are treated as if they were unsigned
   * (i.e. masked with 0xffffffffL).
   * OK if dest==x (not sure if this is guaranteed for mpn_mul_1).
   * This function is basically the same as gmp's mpn_mul_1.
   */
  public static int mul_1 (int[] dest, int[] x, int len, int y)
  
    long yword = (long) y & 0xffffffffL;
    long carry = 0;
    for (int j = 0;  j < len; j++)
      
        carry += ((long) x[j] & 0xffffffffL) * yword;
        dest[j] = (int) carry;
        carry >>>= 32;
      
    return (int) carry;
  

  /**
   * Multiply x[0:xlen-1] and y[0:ylen-1], and
   * write the result to dest[0:xlen+ylen-1].
   * The destination has to have space for xlen+ylen words,
   * even if the result might be one limb smaller.
   * This function requires that xlen >= ylen.
   * The destination must be distinct from either input operands.
   * All operands are unsigned.
   * This function is basically the same gmp's mpn_mul. */
  public static void mul (int[] dest,
              int[] x, int xlen,
              int[] y, int ylen)
  
    dest[xlen] = MPN.mul_1 (dest, x, xlen, y[0]);

    for (int i = 1;  i < ylen; i++)
      
    long yword = (long) y[i] & 0xffffffffL;
    long carry = 0;
    for (int j = 0;  j < xlen; j++)
      
        carry += ((long) x[j] & 0xffffffffL) * yword
          + ((long) dest[i+j] & 0xffffffffL);
        dest[i+j] = (int) carry;
        carry >>>= 32;
      
    dest[i+xlen] = (int) carry;
      
  

【问题讨论】:

您的实现似乎是 O(N^2),但效率低于内置实现。 最简单的答案可能是BigInteger的实现者知道他们在做什么。 @PeterLawrey : Karatsuba 是 O(n 1.585 ),而 BitInteger.multiply 应该 是 O(n²)。我使用BitInteger.multiply 使用的代码编辑了我的原始帖子。 @LouisWasserman:他们可能知道,但还是不明白... 【参考方案1】:

根据https://en.wikipedia.org/wiki/Karatsuba_algorithm

“两个 n 位数字相乘的标准过程需要许多与 n^2\,!, 或 \Theta(n^2)\,! 成比例的基本运算。”

因此,您可以期待相同的 big-O,但实现会产生更多垃圾,并且如果不访问原始底层数据,效率可能会降低。

【讨论】:

两个 n 位数字相乘的标准过程不是 Karatsuba 算法:后者的复杂度为 O(n日志 3)。这就是为什么我发现代码比BigInteger 乘法方法花费更多时间的原因。 @Argonath Wikipedia 建议它是 O(n^2) ,您的代码和结果也是如此。你从哪里得到 O(n log 3)? @PeterLawrey ***不建议这样做。 O(n^1.585) 从字面上看来自文章,我什至不知道你从哪里得到 O(n^2) - 如果它来自你引用的部分,那么它与从中得到的内容完全相反文章应该是。标准程序是 O(n^2),这就是 Karatsuba 更好的原因,而不是相同的原因,因为它不是。

以上是关于Karatsuba 算法的效率的主要内容,如果未能解决你的问题,请参考以下文章

不使用 BigInteger 的 Karatsuba 算法

很长的数字上的 karatsuba 算法错误

Karatsuba乘法

Karatsuba乘法

[转]大整数算法[11] Karatsuba乘法

获得 karasuba 算法的复杂性?