不使用 BigInteger 的 Karatsuba 算法

Posted

技术标签:

【中文标题】不使用 BigInteger 的 Karatsuba 算法【英文标题】:Karatsuba Algorithm without BigInteger usage 【发布时间】:2013-07-08 15:58:21 【问题描述】:

我一直在尝试在不使用 BigInteger 的情况下在 java 中实现 Karatsuba 算法。我的代码仅适用于两个整数相同且位数相同的情况。我没有得到正确的答案,但是我得到的答案非常接近正确的答案。例如,当 12*12 时我得到 149。我无法弄清楚我的代码有什么问题,因为我相信我做的一切都是正确的(按书本)。这是我的代码。

public static void main(String[] args) 
    long ans=karatsuba(12,12);
    System.out.println(ans);


private static long karatsuba(long i, long j) 
    if (i<10 || j<10)
        return i*j;
    
    int n=getCount(i);
    long a=(long) (i/Math.pow(10, n/2));
    long b=(long) (i%Math.pow(10, n/2));
    long c=(long) (j/Math.pow(10, n/2));
    long d=(long) (j%Math.pow(10, n/2));

    long first=karatsuba(a,c);
    long second=karatsuba(b,d);
    long third=karatsuba(a+b,c+d);

    return ((long) ((first*Math.pow(10, n))+((third-first-second)*Math.pow(10,n/2))+third));


private static int getCount(long i) 
    String totalN=Long.toString(i);
    return totalN.length();

编辑:

多亏了魏子尧,问题是把“第三”换成了“第二”。但是我现在有另一个问题是:

如果调用 karatsuba(1234,5678),我会得到正确的答案,但是当我调用 karatsuba(5678,1234) 时,我没有得到正确的答案。任何人都可能知道其中的原因吗?我更新的代码是:

public static void main(String[] args) 
    //wrong answer
    long ans=karatsuba(5678,1234);
    System.out.println(ans);

    //correct answer
    long ans1=karatsuba(1234,5678);
    System.out.println(ans1);


private static long karatsuba(long i, long j) 
    if (i<10 || j<10)
        return i*j;
    

    int n=getCount(i);

    long a=(long) (i/Math.pow(10, n/2));
    long b=(long) (i%Math.pow(10, n/2));
    long c=(long) (j/Math.pow(10, n/2));
    long d=(long) (j%Math.pow(10, n/2));

    long first=karatsuba(a,c);
    long second=karatsuba(b,d);
    long third=karatsuba(a+b,c+d);

    return ((long) ((first*Math.pow(10, n))+((third-first-second)*Math.pow(10, n/2))+second));


更新:

我已经设法将“n/2”的值四舍五入,因此它解决了问题,但是如果使用超过四位数的数字,则会出现错误。这是我更新的代码:

public static void main(String[] args) 
    System.out.println(Math.round(5.00/2));

    //correct answer
    long ans=karatsuba(5678,1234);
    System.out.println(ans);

    //correct answer
    long ans1=karatsuba(1234,5678);
    System.out.println(ans1);

    //wrong answer
    long ans2=karatsuba(102456,102465);
    System.out.println(ans2);



private static long karatsuba(long i, long j) 
    if (i<10 || j<10)
        return i*j;
    

    double n=Math.round(getCount(i));

    long a=(long) (i/Math.pow(10, Math.round(n/2)));
    long b=(long) (i%Math.pow(10, Math.round(n/2)));
    long c=(long) (j/Math.pow(10, Math.round(n/2)));
    long d=(long) (j%Math.pow(10, Math.round(n/2)));

    long first=karatsuba(a,c);
    long second=karatsuba(b,d);

    long third=karatsuba(a+b,c+d);

    return ((long) ((first*Math.pow(10, Math.round(n)))+((third-second-first)*Math.pow(10, Math.round(n/2)))+second));



private static double getCount(long i) 
    String totalN=Long.toString(i);

    return totalN.length();

如果有人在不使用 BigInteger 的情况下提出更大数字(超过四位数)的解决方案,请告诉我。谢谢。

【问题讨论】:

在进行除法之前是否应该将 Math.pow 结果舍入或截断为整数? 你的getCount方法是如何实现的?如果我没记错并且您的 getCount 方法返回其参数中的位数,您应该将 n 设置为 max(getCount(i), getCount(j)) 这是一个简单的版本,假设两个数字都是偶数且位数相等。 【参考方案1】:

你的公式是错误的。

first * Math.pow(10, n) + (第三 - 第一 - 第二) * Math.pow(10, n / 2) + 第三

错了,公式应该是

first * Math.pow(10, n) + (第三 - 第一 - 第二) * Math.pow(10, n / 2) + 第二

***:

z0 = karatsuba(low1,low2)
z1 = karatsuba((low1+high1),(low2+high2))
z2 = karatsuba(high1,high2)
return (z2*10^(m))+((z1-z2-z0)*10^(m/2))+(z0)

【讨论】:

谢谢,我不知道我是怎么犯这个错误的,但是在用“second”替换“third”之后,我遇到了一个问题:如果 karatsuba(1234,5678) 我得到了正确的回答但是当我做 karatsuba(5678,1234) 我没有得到正确的答案。你能知道其中的原因吗?再次感谢。 解决这个问题并不容易 - 注意 56 + 78 是三位数字,所以在那个之后计算变得一团糟。 子尧您好,感谢您的回复我已经设法稍微扭曲了代码以使其工作,但是在解决大于 4 位的数字时仍然存在错误。我将“n/2”的值四舍五入。【参考方案2】:

最后一个错误是 round(n) 应该是 2*round(n/2)。 对于奇数 n,它们显然不同。

关于int n=getCount(i);,它是不对称的来源,所以应该改变它。 为了提高效率,正如我在上面的评论中所读到的那样,它不应该被 max(getCount(i),getCount(j)) 取代,而应该被 min 取代。 事实上,Karatsuba 只有在拆分平衡良好的数字时才有意义。 尝试分解使用 max 和 min 执行的操作以确保...

【讨论】:

【参考方案3】:

最后,经过几个小时的思考,我找到了正确的解决方案:

public static long karatsuba(long i, long j) 
    if (i < 10 || j < 10) 
        return i * j;
    
    double n = Math.round(getCount(i));
    if (n % 2 == 1) 
        n++;
    
    long a = (long) (i / Math.pow(10, Math.round(n / 2)));
    long b = (long) (i % Math.pow(10, Math.round(n / 2)));
    long c = (long) (j / Math.pow(10, Math.round(n / 2)));
    long d = (long) (j % Math.pow(10, Math.round(n / 2)));

    long first = karatsuba(a, c);
    long second = karatsuba(b, d);
    long third = karatsuba(a + b, c + d);

    return ((long) ((first * Math.pow(10, n)) + ((third - first - second) * Math.pow(10, Math.round(n / 2))) + second));

我无法解释为什么 n 不能是奇数,但现在乘法对于我编写的一堆测试来说是正确的。我会尽快解释这种行为。

更新:我正在学习 Coursera 上的算法:设计与分析,第 1 部分课程,并发布了一个关于此行为的问题。这是 Andrew Patton 的回答:

正如在别处提到的,分解输入的关键是确保 b 和 d 的长度相同,以便 a 和 c 具有相同的 10 次幂作为系数。无论那种力量是什么,都会成为你的 n/2; ...因此,10^n 中的 n 值实际上并不是输入的总长度,而是 n/2*2。

所以如果你的例子后面是 3 位数字:

n = 3;   
n/2 = 2;
n != n/2 * 2;

所以在这个例子中 n 应该等于 n/2 * 2 = 4。

希望这是有道理的。

【讨论】:

【参考方案4】:

这是使用 long 的正确实现:

import java.util.Scanner;

/**
 * x=5678 y=1234
 * 
 * a=56,b=78
 * 
 * c=12,d=34
 * 
 * step 0 = m = n/2 + n%2
 * 
 * step 1 = a*c
 * 
 * step 2 = b*d
 * 
 * step 3 = (a + b)*(c + d)
 * 
 * step 4 = 3) - 2) - 1)
 * 
 * step 5 = 1)*pow(10, m*2) + 2) + 4)*pow(10, m)
 *
 */
public class Karatsuba 

    public static void main(String[] args) 
        long x, y;
        try (Scanner s = new Scanner(System.in)) 
            x = s.nextLong();
            y = s.nextLong();
        
        long result = karatsuba(x, y);
        System.out.println(result);
    

    private static long karatsuba(long x, long y) 
        if (x < 10 && y < 10)
            return x * y;

        int n = Math.max(Long.valueOf(x).toString().length(), (Long.valueOf(y).toString().length()));
        int m = n / 2 + n % 2;

        long a = x / (long) Math.pow(10, m);
        long b = x % (long) Math.pow(10, m);
        long c = y / (long) Math.pow(10, m);
        long d = y % (long) Math.pow(10, m);
        long step1 = karatsuba(a, c);
        long step2 = karatsuba(b, d);
        long step3 = karatsuba(a + b, c + d);
        long step4 = step3 - step2 - step1;
        long step5 = step1 * (long) Math.pow(10, m * 2) + step2 + step4 * (long) Math.pow(10, m);
        return step5;
    


使用大整数:

import java.math.BigInteger;
import java.util.Scanner;

/**
 * x=5678 y=1234
 * 
 * a=56,b=78
 * 
 * c=12,d=34
 * 
 * step 0 = m = n/2 + n%2
 * 
 * step 1 = a*c
 * 
 * step 2 = b*d
 * 
 * step 3 = (a + b)*(c + d)
 * 
 * step 4 = 3) - 2) - 1)
 * 
 * step 5 = 1)*pow(10, m*2) + 2) + 4)*pow(10, m)
 *
 */
public class Karatsuba 

    public static void main(String[] args) 
        BigInteger x, y;
        try (Scanner s = new Scanner(System.in)) 
            x = s.nextBigInteger();
            y = s.nextBigInteger();
        
        BigInteger result = karatsuba(x, y);
        System.out.println(result);
    

    private static BigInteger karatsuba(BigInteger x, BigInteger y) 
        if (x.compareTo(BigInteger.valueOf(10)) < 0 && y.compareTo(BigInteger.valueOf(10)) < 0)
            return x.multiply(y);

        int n = Math.max(x.toString().length(), y.toString().length());
        int m = n / 2 + n % 2;

        BigInteger[] a_b = x.divideAndRemainder(BigInteger.valueOf(10).pow(m));
        BigInteger a = a_b[0];
        BigInteger b = a_b[1];
        BigInteger[] c_d = y.divideAndRemainder(BigInteger.valueOf(10).pow(m));
        BigInteger c = c_d[0];
        BigInteger d = c_d[1];

        BigInteger step1 = karatsuba(a, c);
        BigInteger step2 = karatsuba(b, d);
        BigInteger step3 = karatsuba(a.add(b), c.add(d));
        BigInteger step4 = step3.subtract(step2).subtract(step1);
        BigInteger step5 = step1.multiply(BigInteger.valueOf(10).pow(m * 2)).add(step2)
                .add(step4.multiply(BigInteger.valueOf(10).pow(m)));
        return step5;
    


【讨论】:

【参考方案5】:
first * Math.pow(10, 2*degree) + (third - first - second) * Math.pow(10, degree) + second

在哪里

degree = floor(n/2)

没有四舍五入,至少这是我所知道的......

例如,

normal: 5/2 = 2.5
floor: 5/2 = 2
round: 5/2 = 3

因此,你做了什么......

round(n)

不一样
2*floor(n/2)

我是这么认为的,

问候

【讨论】:

【参考方案6】:

这是正确的方法(您的答案已修改):

public class KaratsubaMultiplication 

public static void main(String[] args) 
    //correct answer
    long ans=karatsuba(234,6788);
    System.out.println("actual    " + ans);
    System.out.println("expected  " + 234*6788);

    long ans0=karatsuba(68,156);
    System.out.println("actual    " +ans0);
    System.out.println("expected  " + 68*156);

    long ans1=karatsuba(1234,5678);
    System.out.println("actual    " + ans1);
    System.out.println("expected  " + 1234*5678);

    long ans2=karatsuba(122,456);
    System.out.println("actual    " +ans2);
    System.out.println("expected  " + 122*456);

    long ans3=karatsuba(102456,102465);
    System.out.println("actual    " + ans3);
    System.out.println("expected  " + 102456l * 102465l);



private static long karatsuba(long i, long j) 
    if (i<10 || j<10)
        return i*j;
    

    double n=Long.toString(i).length();


    long a=(long) (i/Math.pow(10, Math.floor(n/2d)));
    long b=(long) (i%Math.pow(10, Math.floor(n/2d)));
    long c=(long) (j/Math.pow(10, Math.floor(n/2d)));
    long d=(long) (j%Math.pow(10, Math.floor(n/2d)));

    long first=karatsuba(a,c);

    long second=karatsuba(b,d);

    long third=karatsuba(a+b,c+d);

    return (long) (
           (first * Math.pow(10, Math.floor(n/2d) * 2)) +
           ((third-second-first) * Math.pow(10, Math.floor(n/2))) +
           second
           );




【讨论】:

【参考方案7】:

您设置 i=a*B+b 和 j=c*B+d,其中 B=10^m 和 m=n/2。那么

i*j=B^2*(a*c)+B*(a*c+b*d-(a-b)*(c-d))+c*d

但是,在一半的情况下,B^2=10^(2m) 不等于 10^n,因为对于奇数 n,一个有 n=2*m+1,所以在这种情况下,B^2=10 ^(n-1).

所以我建议定义一次 m=n/2 或更好的 m=(n+1)/2B=(long)Math.pow(10,m) 并使用它来计算 a、b、c、d,并在最终求和中使用因子 B*B。

【讨论】:

【参考方案8】:

如果输入大小是奇数,我不使用 Math.round() 进行四舍五入,而是将输入大小的值(x 或 y 中数字的最小值)加 1。例如,如果 X = 127 & Y = 162,则输入大小为 3。将其增加 1 使其变为 4。然后,a = X/Math.pow(10,input_size/2) = 1。b = X%Math .pow(10,input_size/2) = 27。类似地,c = 1 和 d = 62。现在,如果我们计算 X*Y = (ac)*Math.pow(10,input_size)+(ad+bc)* Math.pow(10,input_size/2)+bd;它给出了正确的答案。 请记住,我们仅在奇数时将输入大小增加 1。完整的实现在这里 - https://github.com/parag-vijay/data_structures/blob/master/java/KaratsubaMultiplication.java

【讨论】:

【参考方案9】:
/** Function to multiply two numbers **/
    public long multiply(long x, long y)
    
        int size1 = getSize(x);
        int size2 = getSize(y);
        /** Maximum of lengths of number **/
        int N = Math.max(size1, size2);
        /** for small values directly multiply **/        
        if (N < 10)
            return x * y;
        /** max length divided, rounded up **/    
        N = (N / 2) + (N % 2);    
        /** multiplier **/
        long m = (long)Math.pow(10, N);

        /** compute sub expressions **/        
        long b = x / m;
        long a = x - (b * m);
        long d = y / m;
        long c = y - (d * N);
        /** compute sub expressions **/
        long z0 = multiply(a, c);
        long z1 = multiply(a + b, c + d);
        long z2 = multiply(b, d);          

        return z0 + ((z1 - z0 - z2) * m) + (z2 * (long)(Math.pow(10, 2 * N)));        
    
    /** Function to calculate length or number of digits in a number **/
    public int getSize(long num)
    
        int ctr = 0;
        while (num != 0)
        
            ctr++;
            num /= 10;
        
        return ctr;
    

这就是 BigInteger 的实现:

http://www.nayuki.io/res/karatsuba-multiplication/KaratsubaMultiplication.java

【讨论】:

使用 N/d + N%d 进行四舍五入看起来很新鲜。

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

如何在不使用 java.math.BigInteger 的情况下在 Java 中处理非常大的数字

Java中的大数处理类BigInteger和BigDecimar浅析

BigInteger的用法

求编个java程序要求:编写一个求大数的阶乘,不能用biginteger,并且使用数组不能浪费,不

java BigInteger实现

BigInteger方法总结