我正在使用基本的Knuth 4.3.1算法M对自然数进行任意精度乘法。我在Java中的实现如下。问题是它正在产生前导零,似乎是算法的副作用,不知道给定结果是否有两个位置或一个。例如,2 x 3 = 6(一位数),但4 x 7 = 28(两位数)。算法似乎总是保留两个数字,导致前导零。
我的问题有两个方面:(1)我的算法是M的正确实现,还是我做错了不必要地创建前导零,以及(2)如果它是M的不可避免的副作用呢?产生前导零,然后我们如何调整或使用改进的算法来避免前导零。
// Knuth M algorithm 4.3.1
final public static void multiplyDecimals( int[] decimalM1, int[] decimalN1, int[] result, int radix ){
Arrays.fill( result, 0 );
int lenM = decimalM1[0];
int lenN = decimalN1[0];
result[0] = lenM + lenN;
int iStepM = lenM;
while( iStepM > 0 ){
int iStepN = lenN;
int iCarry = 0;
while( iStepN > 0 ){
int iPartial = decimalM1[iStepM] * decimalN1[iStepN] + result[iStepM + iStepN] + iCarry;
result[iStepM + iStepN] = iPartial % radix;
iCarry = iPartial / radix;
iStepN--;
}
result[iStepM] = iCarry;
iStepM--;
}
return;
}
算法的输出显示生成的阶乘,显示前导零。
1 01
2 002
3 0006
4 00024
5 000120
6 0000720
7 00005040
8 000040320
9 0000362880
10 000003628800
11 00000039916800
12 0000000479001600
13 000000006227020800
14 00000000087178291200
15 0000000001307674368000
16 000000000020922789888000
17 00000000000355687428096000
18 0000000000006402373705728000
19 000000000000121645100408832000
20 00000000000002432902008176640000
答案 0 :(得分:2)
该算法根本没有分配任何前导零。你是。你提供输出数组,并用零填充它。 Knuth算法M不这样做。
另外:
你当然应该跳过这两个数字中的所有前导零。这会对性能产生巨大影响,因为它是一种O(MN)算法。最终M和N的总和几乎是正确的输出数字;乘法后的最后一步是删除可能的一个前导零。
如果当前M位为零,您也可以跳过内循环。这是Knuth的第M2步。请注意,自然界数字中的零数字出现频率高于1/10:有一个关于此的定律表明每个数字1,2,3,5,6,7,8,9的可能性相继较低。
答案 1 :(得分:1)
每个单独的乘法为最坏情况输入分配足够的空间。分配最坏的情况是正确的做法,因为一般情况下你不会确定你的结果在你完成乘法之前是否有前导零!
要防止问题中冗余前导零的级联效应,请在执行乘法后检查前导零,并相应地减小长度。请注意,如果您的输入都没有任何前导零,则它们的乘法结果不应超过一。然而,对于减法来说这是不正确的(它可以显然产生任意数量的前导零!)。
答案 2 :(得分:0)
我想出了如何解决问题。该程序需要修改如下:
final public static int multiplyDecimals( int[] decimalM1, int[] decimalN1, int[] result, int radix ){
Arrays.fill( result, 0 );
int lenM = decimalM1[0];
int lenN = decimalN1[0];
result[0] = lenM + lenN;
int iStepM = lenM;
while( iStepM > 0 ){
int iStepN = lenN;
int iCarry = 0;
while( iStepN > 0 ){
int iPartial = decimalM1[iStepM] * decimalN1[iStepN] + result[iStepM + iStepN] + iCarry;
result[iStepM + iStepN] = iPartial % radix;
iCarry = iPartial / radix;
iStepN--;
}
result[iStepM] = iCarry;
iStepM--;
}
int xFirstDigit = 1;
while( result[xFirstDigit] == 0 ) xFirstDigit++;
if( xFirstDigit > 1 ){
int ctDigits = result[0] - xFirstDigit + 1;
for( int xDigit = 1; xDigit <= ctDigits; xDigit++ ) result[xDigit] = result[xDigit + xFirstDigit - 1];
result[0] = ctDigits;
}
return result[0];
}