6

首先是一点背景:
- 我是第一次发帖,是大学学生(不是编程)。
- 这不是作业问题,我只是为了好玩。
- 我的编程经验包括一个学期(3 个月)的 C++ 和一些高中 QBasic。
- 是的,我看过 GMP 和 Bignum 库;从原始代码中学习东西是非常困难的,尤其是在不了解程序员的意图的情况下。此外,我想学习如何为自己做这件事。

我正在为任意大的整数编写乘法函数。我使用字符数组来表示这些数字,最后用 + 或 - 作为标记(例如“12345+”、“31415-”)。

我目前正在实施 Karatsuba 算法。问题在于,使用递归和动态内存分配,该函数比简单方法慢 5 倍。
我可以使用一些提示来减少运行时间。

char* dam(char* one, char* two){            // Karatsuba method

    char* zero = intochar(0, 0);
    int size_a = char_size(one) - 1;
    int size_b = char_size(two) - 1;

    if(compare(one, zero) == 0 || compare(two, zero) == 0)
        return zero;                        // if either array is zero, product is zero
    delete[] zero;

    if(size_a < 4 && size_b < 4)            // if both numbers are 3 digits or less, just return their product
        return multiplication(one, two);
                                            // is the product negative?
    bool negative = one[size_a] == two[size_b]? false : true;
    int digits = size_a > size_b ? size_a : size_b;
    digits += digits & 1;                   // add one if digits is odd
    int size = digits / 2 + 1;              // half the digits plus sentinel

    char* a, *b;                            // a and b represent one and two but with even digits
    if(size_a != digits)
        a = pad_char(one, digits - size_a); // pad the numbers with leading zeros so they have even digits
    else
        a = copy_char(one);
    if(size_b != digits)
        b = pad_char(two, digits - size_b);
    else
        b = copy_char(two);

    char* a_left = new char[size];          // left half of number a
    char* a_rite = new char[size];          // right half of number a
    char* b_left = new char[size];
    char* b_rite = new char[size];
    memcpy(a_left, a, size - 1);
    a_left[size - 1] = a[digits];
    memcpy(a_rite, a + size - 1, size);
    memcpy(b_left, b, size - 1);
    b_left[size - 1] = b[digits];
    memcpy(b_rite, b + size - 1, size);
    delete[] a;
    delete[] b;

    char* p0 = dam(a_left, b_left);         // Karatsuba product = p1*10^n + (p0+p2-p1)*10^(n/2) + p2
    char* p2 = dam(a_rite, b_rite);
    deduct(a_left, a_rite);
    deduct(b_left, b_rite);
    char* p1 = dam(a_left, b_left);
    char* p3 = intochar(0, digits - 1);     // p3 = p0 + p2 - p1
    append(p3, p0);                         // append does naive addition
    append(p3, p2);
    deduct(p3, p1);
    delete[] a_left;
    delete[] a_rite;
    delete[] b_left;
    delete[] b_rite;

    int sum_size = 2 * digits;              // product of two numbers can have a maximum of n1 + n2 digits
    char* sum = new char[sum_size + 1];
    memset(sum, 0, sum_size);
    if(negative)
        sum[sum_size] = '-';
    else
        sum[sum_size] = '+';

    char* left = extend_char(p0, digits, false);        // extend returns a new array with trailing zeros
    char* mid = extend_char(p3, size - 1, false);
    append(sum, left);
    append(sum, mid);
    append(sum, p2);
    delete[] p0;
    delete[] p1;
    delete[] p2;
    delete[] p3;
    delete[] left;
    delete[] mid;

    return sum;}
4

4 回答 4

6

Karatsuba 是一个很好的算法,而且编程起来并不难。如果你只是为了好玩,那么在 base 10 中工作甚至不是一个坏主意——它会大大减慢你的速度,但也会减慢幼稚的实现,所以你仍然有比较这两种方法的基础。

但是,您绝对必须放弃在递归树的每个节点上动态分配和释放工作空间的想法。你只是买不起。您必须在计算开始时分配所需的工作空间,并智能地处理您的指针,以便树的每一级都获得所需的工作空间,而无需分配它。

此外,在每个级别测试负面产品是没有意义的。只需在顶层执行此操作,并在计算期间仅使用正数。

并不是说它与您的问题有关,而是每当我看到类似

bool negative = one[size_a] == two[size_b]? false : true;

我的心微微一缩。想想所有那些浪费的像素!我恭敬地建议:

bool negative = one[size_a] != two[size_b] ;
于 2011-11-02T19:46:16.203 回答
1

你是什​​么意思写“功能慢5倍”?Karatsuba 渐近地更快,而不仅仅是更快。这意味着即使是 Karatsuba 的玩具实现最终也会比简单的乘法更快。您是否使用 10000 位数字测试速度?

我知道 GMP 代码不容易阅读……但请看从代码中提取的这张表。它为(针对不同的 CPU)提供了 Toom-2,2 (Karatsuba) 的阈值。简而言之,在 GMP 中实现 Karatsuba 并不比用于小于 320 位(10 x 32 位寄存器)的操作数的简单实现快。

关于您的代码的一些问题:

  • 你真的需要 char *a, *b 吗?在开始计算之前删除它们!;-)
  • 您确定需要将标志复制到 {a,b}_{left,rite} 吗?您是否检查了负操作数的结果是否正确?
  • 考虑非常不平衡的操作数......如果 size_a * 2 < size_b (反之亦然)你应该怎么做?

下一步将是Tom,对吧?

于 2011-11-03T06:56:12.990 回答
1

我想放缓实际上可能是分配。用本地固定大小的缓冲区替换它们,我想你会看到一个不错的速度提升。或者使用自定义池分配器。但我认为,如果你很狡猾,大部分都可以就地完成。

此外,如果将字符串的长度传递给函数,则每次都可以为自己节省一次迭代来查找长度。

(也拼写为“正确”)。

于 2011-11-02T19:10:25.950 回答
1

您使用拼写出的十进制值会产生大量开销。Karatsuba 乘法只会击败相对于机器寄存器大小很大的数字的长乘法,并且您确实希望每个原始乘法都尽可能多地完成工作。

我建议你重新设计你的数据结构,这样:

if(size_a < 4 && size_b < 4)
    return multiplication(one, two);

可以变成这样:

if(size_a == 1 && size_b == 1)
    return box(int64_t(one[0]) * two[0]);

的类型可能在one[0]哪里int32_t。这就是 GMP 对其mp_limb_t阵列所做的事情。

于 2011-11-02T19:31:46.327 回答