在C ++中使用2个64位数字的乘法

时间:2017-01-18 11:43:51

标签: c++ algorithm math karatsuba

我实施了karatsuba乘法算法。我希望以这种方式改进它,我可以乘以2个64位数字,但我不知道如何做到这一点。我得到了一个暗示,这两个数字包含的数字是2的幂,但它没有任何建议。你能提供其他任何提示吗?数学提示或算法改进提示。

#include <iostream>
#include <math.h>
using namespace std;

int getLength(long long value);
long long multiply(long long x, long long y);

int getLength(long long value)
{
    int counter = 0;
    while (value != 0)
    {
        counter++;
        value /= 10;
    }
    return counter;
}

long long multiply(long long x, long long y)
{
    int xLength = getLength(x);
    int yLength = getLength(y);

    // the bigger of the two lengths
    int N = (int)(fmax(xLength, yLength));

    // if the max length is small it's faster to just flat out multiply the two nums
    if (N < 10)
        return x * y;

    //max length divided and rounded up
    N = (N / 2) + (N % 2);

    long long multiplier = pow(10, N);

    long long b = x / multiplier;
    long long a = x - (b * multiplier);
    long long d = y / multiplier;
    long long c = y - (d * N);

    long long z0 = multiply(a, c);
    long long z1 = multiply(a + b, c + d);
    long long z2 = multiply(b, d);


    return z0 + ((z1 - z0 - z2) * multiplier) + (z2 * (long long)(pow(10, 2 * N)));

}

int main()
{
    long long a;
    long long b;
    cin >> a;
    cout << '\n';
    cin >> b;
    cout << '\n' << multiply(a, b) << endl;
    return 0;
}

2 个答案:

答案 0 :(得分:4)

这是一个提示:

(A + kB) * (C + kD) = AC + k(BC + AD) + k^2(BD)

如果k是你保持数字的基础的力量,这会有所帮助。例如,如果k是1'000'000'000且你的数字是10的基数,那么k的乘法是通过简单地移动数字(加零)来完成的。

无论如何,考虑将你的64位数字分为两部分,每部分32位,并按上述方式进行数学运算。要计算ACBCADBD,您要乘以一对32位数字,这可以类似地完成。

由于您的位数是2的幂,您可以将数字分成两半,直到达到可管理的数字大小(例如1位数字)。

顺便说一下,你的问题不清楚你是在谈论64位还是64位小数。如果你要找的只是64位数的乘法,那就这样做:

// I haven't actually run this code, so...

typedef unsigned long long u64;

u64 high32 (u64 x) {return x >> 32;}
u64 low32  (u64 x) {return x & 0xFFFFFFFF;}

u64 add_with_carry (u64 a, u64 b, u64 * carry)
{
    u64 result = a + b;
    if (result < a) // and/or b?
        *carry = 1;
    return result;
}

void mul (u64 a, u64 b, u64 * result_low, u64 * result_high)
{
    u64 a0 = low32(a), a1 = high32(a);
    u64 b0 = low32(b), b1 = high32(b);

    u64 a0b0 = a0 * b0;
    u64 a0b1 = a0 * b1;
    u64 a1b0 = a1 * b0;
    u64 a1b1 = a1 * b1;

    u64 c0 = 0, c1 = 0;
    u64 mid_part = add_with_carry(a0b1, a1b0, &c1);

    *result_low  = add_with_carry(a0b0, (low32(mid_part) << 32, &c0);
    *result_high = high32(mid_part) + a1b1 + (c1 << 32) + c0; // this won't overflow
}

此实现与上面概述的相同。因为在标准C / C ++中,乘法结果中我们可以拥有的最大数量的有意义位是64,那么我们一次只能乘以两个32位数。这就是我们在这里做的。

最终结果将是128位,我们以两个无符号的64位数字返回。我们通过4次32位乘法和一些加法进行64位乘64位乘法运算。

作为旁注,这是少数情况之一,写这个程序集通常比C更容易。例如,在x64程序集中,这实际上是两个mov s,四个{{1} } s,四个mul和四个add s(这只是使用基本的CPU指令。)

答案 1 :(得分:1)

要在较低位宽的算术中应用Karatsuba或任何其他乘法,您需要将数字分成较小的“数字”。在此之前你需要访问这些“数字”,所以这里是如何做到的:

您的号码为1234,并希望将其分为10^1位数

1234 = 1*1000 + 2*100 + 3*10 + 4

你可以获得这样的数字:

x=1234;
a0=x%10; x/=10; // 4
a1=x%10; x/=10; // 3
a2=x%10; x/=10; // 2
a3=x%10; x/=10; // 1

如果您想要10^2个数字:

x=1234;
a0=x%100; x/=100; // 34
a1=x%100; x/=100; // 12

现在的问题是要做到这一点,你需要对你没有的全数进行除法。如果您将数字作为字符串,则很容易完成,但假设您没有。计算机基于二进制计算,因此最好使用2的幂作为“数字”的基础,所以:

x = 1234 = 0100 1101 0010 bin

现在,如果我们想拥有例如2^4=16基数,那么:

a0=x%16; x/=16; // 0010
a1=x%16; x/=16; // 1101
a2=x%16; x/=16; // 0100

现在,如果您意识到除以2的幂只是右移位且余数可以表示为AND,那么:

a0=x&15; x>>=4; // 0010
a1=x&15; x>>=4; // 1101
a2=x&15; x>>=4; // 0100

位移可以堆叠到任何位宽数字,所以现在你得到了所需的一切。但是,如果您选择2^8BYTE,那么这就不是全部了,那么您可以使用指针代替:

DWORD x=0x12345678; // 32 bit number
BYTE *db=(BYTE*)(&x); // 8bit pointer that points to x

a0=db[0]; // 0x78
a1=db[1]; // 0x56
a2=db[2]; // 0x34
a3=db[3]; // 0x12

因此您可以直接访问数字或从数字重建x:

DWORD x; // 32 bit number
BYTE *db=(BYTE*)(&x); // 8bit pointer that points to x
db[0]=0x78;
db[1]=0x56;
db[2]=0x34;
db[3]=0x12;
// here x should be 0x12345678

请注意,订单取决于平台MSB或LSB第一顺序。现在你可以应用乘法。例如,对于16位乘法,32 * 32 = 64位就像这样用天真的O(n^2)方法完成:

x(a0+a1<<16) * y(b0+b1<<16) = a0*b0 + a0*b1<<16 + a1*b0<<16 + a1*b1<<32

其中a0,a1,b0,b1是操作数的数字。请注意,每个ai*bj乘法的结果是2位宽,因此您需要将其拆分为数字并存储到由位移寻址的结果数字。请注意,添加会导致溢出更高的数字。要处理这个问题,您需要使用至少两倍的算术宽度进行添加(16 * 16位mul - > 32位添加)或使用进位标记。遗憾的是,在C ++中使用汇编时,您无法访问Carry标志。幸运的是,可以模拟看到:

现在您可以构建您的Karatsuba或更高级的乘法以获取更多信息,请参阅: