当您从较小的乘法中构建扩展精度的有符号乘法时,您最终会得到有符号和无符号算术的混合。
特别是,如果将有符号值分成两半,则将上半部分视为有符号,而将下半部分视为无符号。事实上,扩展精度加法也是如此。
考虑这个任意示例,其中AH
和AL
代表 的高半部分和低半部分A
,BH
和BL
代表 的高半部分和低半部分B
。(注意:这些并不意味着代表 x86 寄存器的一半,只是被乘数的一半。)这些L
术语是无符号的,并且这些H
术语是有符号的。
AH : AL
x BH : BL
-------------------
AL * BL unsigned x unsigned => zero extend to full precision
AH * BL signed x unsigned => sign extend to full precision
AL * BH unsigned x signed => sign extend to full precision
AH * BH signed x signed
该AL * BL
产品未签名,因为 AL 和 BL 都未签名。因此,当您将其提升到结果的完全精度时,它会得到零扩展。
和产品混合有符号AL * BH
和AH * BL
无符号值。生成的产品已签名,当您将其提升到结果的完全精确度时,需要对其进行签名扩展。
以下 C 代码演示了以 16×16 乘法实现的 32×32 乘法。同样的原则也适用于从 64×64 乘法构建 128×128 乘法。
#include <stdint.h>
#include <stdio.h>
int64_t mul32x32( int32_t x, int32_t y )
{
int16_t x_hi = 0xFFFF & (x >> 16);
int16_t y_hi = 0xFFFF & (y >> 16);
uint16_t x_lo = x & 0xFFFF;
uint16_t y_lo = y & 0xFFFF;
uint32_t lo_lo = (uint32_t)x_lo * y_lo; // unsigned x unsigned
int32_t lo_hi = (x_lo * (int32_t)y_hi); // unsigned x signed
int32_t hi_lo = ((int32_t)x_hi * y_lo); // signed x unsigned
int32_t hi_hi = ((int32_t)x_hi * y_hi); // signed x signed
int64_t prod = lo_lo
+ (((int64_t)lo_hi + hi_lo) << 16)
+ ((int64_t)hi_hi << 32);
return prod;
}
int check(int a, int b)
{
int64_t ref = (int64_t)a * (int64_t)b;
int64_t tst = mul32x32(a, b);
if (ref != tst)
{
printf("%.8X x %.8X => %.16llX vs %.16llX\n",
(unsigned int)a, (unsigned int)b,
(unsigned long long)ref, (unsigned long long)tst);
return 1;
}
return 0;
}
int main()
{
int a = (int)0xABCDEF01;
int b = (int)0x12345678;
int c = (int)0x1234EF01;
int d = (int)0xABCD5678;
int fail = 0;
fail += check(a, a);
fail += check(a, b);
fail += check(a, c);
fail += check(a, d);
fail += check(b, b);
fail += check(b, c);
fail += check(b, d);
fail += check(c, c);
fail += check(c, d);
fail += check(d, d);
printf("%d tests failed\n", fail);
return 0;
}
即使您将被乘数分成两个以上的部分,这种模式也会扩展。也就是说,只有签名数字中最重要的部分被视为已签名。所有其他作品均未签名。考虑这个例子,它将每个被乘数分成 3 部分:
A2 : A1 : A0
x B2 : B1 : B0
---------------------------------
A0 * B0 => unsigned x unsigned => zero extend
A1 * B0 => unsigned x unsigned => zero extend
A2 * B0 => signed x unsigned => sign extend
A0 * B1 => unsigned x unsigned => zero extend
A1 * B1 => unsigned x unsigned => zero extend
A2 * B1 => signed x unsigned => sign extend
A0 * B2 => unsigned x signed => sign extend
A1 * B2 => unsigned x signed => sign extend
A2 * B2 => signed x signed
由于所有混合符号和符号扩展的乐趣,将有符号×有符号乘法实现为无符号×无符号乘法通常更容易,如果被乘数不同,则在最后有条件地取反。(事实上,当你得到扩展精度浮点数时,只要你保持像 IEEE-754 这样的符号大小形式,你就不必处理有符号乘法。)
这个汇编 gem展示了如何有效地否定扩展精度值。(宝石页面有点过时,但您可能会发现它很有趣/有用。)