Karatsuba乘法改进

时间:2015-07-10 10:10:08

标签: c++ algorithm multiplication divide-and-conquer

我已经为我的教育目标实施了Karatsuba乘法算法。现在我正在寻找进一步的改进。我已经实现了某种长算法,并且无论我是否使用超过 100 的整数表示的基础,它都能正常工作。 基数 10 并使用范围clang++ -O3中的两个随机整数的[10^50000, 10^50001]进行编译:

Naive algorithm took me 1967 cycles (1.967 seconds)
Karatsuba algorithm took me 400 cycles (0.4 seconds)

基数 100 的相同数字:

Naive algorithm took me 409 cycles (0.409 seconds)
Karatsuba algorithm took me 140 cycles (0.14 seconds)

有没有办法改善这个结果? 现在我使用这样的函数来完成我的结果:

void finalize(vector<int>& res) {
    for (int i = 0; i < res.size(); ++i) {
        res[i + 1] += res[i] / base;
        res[i] %= base;
    }
}

正如您所看到的那样,它计算每个步骤并将其推送到下一个数字。如果我采用基础>=1000,结果将会溢出。

如果你看到我的代码,我使用int的向量来表示长整数。根据我的基数,一个数字将分为向量的不同部分。 现在我看到几个选项:

  • 对向量使用long long类型,但对于长度很大的整数也可能会溢出
  • 实现长运算中的进位表示

在我看到一些评论后,我决定扩大这个问题。假设我们想要将长整数表示为int的向量。对于instanse:

ULLONG_MAX = 18446744073709551615

对于输入,我们传递第210个Fibonacci数34507973060837282187130139035400899082304280,它不适合任何标准类型。如果我们在带有10000000的int的向量中表示它,它将类似于:

v[0]: 2304280
v[1]: 89908
v[2]: 1390354
v[3]: 2187130
v[4]: 6083728
v[5]: 5079730
v[6]: 34

当我们进行乘法时,我们可能得到(为简单起见,它是两个相同的数字) (34507973060837282187130139035400899082304280)^2

v[0] * v[0] = 5309706318400
...
v[0] * v[4] = 14018612755840
...

这只是第一行,我们必须做这样的六个步骤。当然,某些步骤会在乘法或进位计算后引起溢出。

如果我错过了什么,请告诉我,我会改变它。 如果您想查看完整版,则会在我的github

1 个答案:

答案 0 :(得分:0)

Base 2^64和base 2^32是进行高精度算术的最常用基础。通常,数字存储在无符号整数类型中,因为它们在溢出方面具有良好的语义。

例如,可以通过以下方式检测添加的进位:

uint64_t x, y; // initialize somehow
uint64_t sum = x + y;
uint64_t carry = sum < x; // 1 if true, 0 if false

此外,汇编语言通常有一些“随附携带”指令;如果你可以编写内联汇编(或访问内在函数),你可以利用这些。

对于乘法,大多数计算机都有机器指令,可以计算一个机器字 - &gt;两机字产品;有时,获得两半的指令称为“乘法高”和“乘低”。您需要编写汇编来获取它们,尽管许多编译器提供了更大的整数类型,使用它们可以访问这些指令:例如在gcc中,你可以实现乘以hi

uint64_t mulhi(uint64_t x, uint64_t y)
{
    return ((__uint128_t) x * y) >> 64;
}

当人们不能使用它时,他们会在2^32中进行乘法运算,以便他们可以使用相同的方法来实现便携式mulhi指令,使用uint64_t作为两位数类型。

如果你想编写有效的代码,你真的需要利用这些更大的乘法指令。基数2^32中的数字乘以基数10中的数字乘以数字的功能的九十倍以上。基数2^64中的乘数比其强大四倍。而且你的计算机可能比你为基数10乘法实现的任何东西都更快。

相关问题