【问题标题】:Karatsuba multiplication improvementKaratsuba 乘法改进
【发布时间】:2015-09-29 01:10:54
【问题描述】:

我已经为我的教育目标实施了 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;
    }
}

正如您所见,它计算进位并将其推到下一位。如果我以&gt;=1000 为基数,结果将溢出。

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

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

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

ULLONG_MAX = 18446744073709551615

对于输入,我们传递第 210 个斐波那契数 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

【问题讨论】:

  • 如果您有工作代码,那么您的问题可能更适合codereview.stackexchange.com
  • 我投票决定将此问题作为离题结束,因为它属于 codereview。在那里,您需要在帖子中发布代码,而不是链接到您的 github。您只需发布相关部分。
  • 如果你真的想加速,你应该使用另一种更快的算法:例如 Tom-Cook 或傅里叶变换。
  • @UmNyobe 真的很抱歉,没有注意到这一点。我会删除评论。当您看到它时将其标记为已过时。
  • @EdChum 在推荐迁移到代码审查之前请阅读此检查表meta.codereview.stackexchange.com/questions/1687/…

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


【解决方案1】:

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

例如,可以从加法中检测进位,如下所示:

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

另外,汇编语言通常有一些“带进位相加”指令;如果您可以编写内联汇编(或可以访问内在函数),则可以利用这些。

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

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

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

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

【讨论】:

  • 但正如我所说,我使用基于整数向量的长算术来表示巨大的数字,因为即使是普通的 uint64_t 类型也无法容纳超过 20 位的整数。
  • @vpetrigo:但是2^64 中的 两位 数字可以存储 20 个十进制数字。
猜你喜欢
  • 1970-01-01
  • 2020-08-17
  • 1970-01-01
  • 2018-10-06
  • 1970-01-01
  • 1970-01-01
  • 2017-05-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多