在《黑客的乐趣》一书中,有一个算法可以计算两个(有符号)字的双字乘积。
函数
在该代码的末尾有一行被注释掉的代码。
这种替代方法使用了五次乘法和四次加法,即将一次加法换成了一次乘法。
但我认为这种替代方法可以改进。我还没有提到硬件方面的内容。假设有一个假想的CPU,它可以计算两个字的乘积的低位,但不能计算高位(例如对于32位字,32x32的低32位)。在这种情况下,我觉得这个算法可以改进。以下是我的构想,假设是32位字(相同的概念也适用于64位字)。
这个函数使用了四次乘法、五次加法和两次比较,比原来的函数更劣。
函数
muldws1
使用四次乘法和五次加法来计算两个字的双字。在该代码的末尾有一行被注释掉的代码。
/* w[1] = u*v; // Alternative. */
这种替代方法使用了五次乘法和四次加法,即将一次加法换成了一次乘法。
但我认为这种替代方法可以改进。我还没有提到硬件方面的内容。假设有一个假想的CPU,它可以计算两个字的乘积的低位,但不能计算高位(例如对于32位字,32x32的低32位)。在这种情况下,我觉得这个算法可以改进。以下是我的构想,假设是32位字(相同的概念也适用于64位字)。
void muldws1_improved(int w[], int32_t x, int32_t y) {
uint16_t xl = x; int16_t xh = x >> 16;
uint16_t yl = y; int16_t yh = y >> 16;
uint32 lo = x*y;
int32_t t = xl*yh + xh*yl;
uint16_t tl = t; int16_t th = t >>16;
uint16_t loh = lo >> 16;
int32_t cy = loh<tl; //carry
int32_t hi = xh*yh + th + cy;
w[0] = hi; w[1] = lo;
}
这个方法使用了四次乘法,三次加法和一次比较。这个改进比我希望的要小。
这能被改进吗?有更好的方法来确定进位标志吗? 我应该指出,我还假设硬件没有进位标志(例如没有ADDC指令),但可以比较字(例如word1<word
)。
编辑:正如Sander De Dycker所指出的那样,我的函数未通过单元测试。这里有一个通过单元测试但效率较低的版本。我认为它可以改进。
void muldws1_improved_v2(int w[], int32_t x, int32_t y) {
uint16_t xl = x; int16_t xh = x >> 16;
uint16_t yl = y; int16_t yh = y >> 16;
uint32_t lo = x*y;
int32_t t2 = xl*yh;
int32_t t3 = xh*yl;
int32_t t4 = xh*yh;
uint16_t t2l = t2; int16_t t2h = t2 >>16;
uint16_t t3l = t3; int16_t t3h = t3 >>16;
uint16_t loh = lo >> 16;
uint16_t t = t2l + t3l;
int32_t carry = (t<t2l) + (loh<t);
int32_t hi = t4 + t2h + t3h + carry;
w[0] = hi; w[1] = lo;
}
这个函数使用了四次乘法、五次加法和两次比较,比原来的函数更劣。
&
操作的黑客之乐版本。当然,这可能取决于 CPU 架构,16 位移位可能是免费或非常便宜的。 - John Bollinger{0x7fffffff, 0x7eeeeeee, 0x3f777776,0x81111112}
单元测试。 - Sander De Dyckerint64_t
,但实际上它并不返回任何值。 - joopint w[]
应该改为int32_t w[]
或者更好的方式是:int32_t *whi, uint32_t *wlo
。 - chux - Reinstate Monica