首先是一点背景:
- 我是第一次张贴海报,大学生(不是编程)
- 这不是一个功课问题,我只是为了好玩而做这件事
- 我的编程经验包括一个学期(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;}
答案 0 :(得分:5)
Karatsuba是一个很好的算法,并不是很难编程。如果你只是为了好玩而做,那么在10号基础上工作甚至不是一个坏主意 - 它会让你失望,但它也会减慢天真的实施速度,所以你仍然有比较这两种方法的基础。
但是,您绝对必须放弃在递归树的每个节点上动态分配和释放工作空间的想法。你买不起。您必须在计算开始时分配所需的工作空间,并智能地处理指针,以便树的每个级别都可以获得所需的工作空间,而无需分配它。
此外,在每个级别测试负面产品都没有意义。只需在顶层执行此操作,并在计算过程中专门使用正数。
并不是说它与你的问题有关,而是每当我看到像
这样的东西时bool negative = one[size_a] == two[size_b]? false : true;
我的心缩了一点。想想所有浪费的像素!我恭敬地建议:
bool negative = one[size_a] != two[size_b] ;
答案 1 :(得分:1)
我认为减速实际上可能是分配。用本地固定大小的缓冲区替换它们,我想你会看到一个不错的速度提升。或者使用自定义池分配器。但我认为,如果你很狡猾,可以采取相应的措施。
另外,如果将字符串的长度传递给函数,则每次都要保存一次迭代以查找长度。
(也拼写为“正确”)。
答案 2 :(得分:1)
您使用拼写的十进制值会产生大量开销。对于相对于机器寄存器大小 large 的数字,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
数组所做的事情。
答案 3 :(得分:1)
你是什么意思写“功能慢5倍”? Karatsuba渐近更快,而不仅仅是更快。这意味着即使是Karatsuba的玩具实现最终也会比天真的乘法更快。你用10000位数字测试速度了吗?
我知道GMP代码不易阅读......但请看this table extracted from the code。 它为(不同的CPU)提供了Toom-2,2(Karatsuba)的阈值。简而言之,在GMP中实现Karatsuba并不比小于320位(10 x 32位寄存器)的操作数的原始实现更快。
有关您的代码的一些问题:
下一步将是Toom,对吧?