我正在编写一个函数库来为签名整数类型s0128
,s0256
,s0512
,s1024
和浮点类型{提供所有传统的运算符和函数{1}},f0128
,f0256
,f0512
。
我现在正在编写f1024
,s0128
,s0256
,s0512
个多重例程,但我得到的错误结果让我感到困惑。我假设我可以使用64位s1024
指令(在imul rcx
中产生128位结果)级联乘法,就像使用{{1}的无符号操作数一样。 }指令...但是rdx:rax
的答案是错误的。
我怀疑有一些技巧可以完成这项工作,可能会混合使用mul rcx
和imul
指令。或者是否有某些原因无法使用带符号的乘法指令实现更大的乘法?
因此,您了解该技术,我将描述imul
个操作数的最小版本。
mul
每次代码乘以两个64位值时,它会在s0128
中生成128位结果。每次代码生成128位结果时,它会将该结果与 arg2.1 arg2.0 : two 64-bit parts of s0128 operand
arg1.1 arg1.0 : two 64-bit parts of s0128 operand
---------------
0 out.edx out.eax : output of arg1.0 * arg2.0
out.edx out.eax : output of arg1.0 * arg2.1
-------------------------
out.2 out.1 out.0 : sum the above intermediate results
out.edx out.eax : output of arg1.1 * arg2.0
-------------------------
out.2 out.1 out.0 : sum the above intermediate results
,edx:eax
,addq
指令(最终{{1}中的累加三重64位寄存器相加指令只增加零以确保任何进位标志传播。)
当我将小的负数乘以小的正数作为测试时,结果为负,但在128位{{1}的高64位值的底部有一个或两个非零位结果。这对我来说意味着在多精度有符号乘法中传播的东西并不完全正确。
当然,adcq
,adcq
,adcq
的级联相当广泛。
我错过了什么?我必须将两个操作数转换为无符号,执行无符号乘法,然后如果一个(但不是两个)操作数为负,则否定结果?或者我可以使用s0128
带符号的乘法指令计算多精度结果吗?
答案 0 :(得分:4)
当您使用较小的乘法构建扩展精度符号乘法时,最终会得到有符号和无符号算术的混合。
特别是,如果将签名值分成两半,则将上半部分视为已签名,将下半部分视为无符号。事实上,扩展精度加法也是如此。
考虑这个任意的例子,其中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这样的符号幅度形式,你就不必处理有符号的乘法。)
This assembly gem显示了如何有效地否定扩展精度值。 (gems page有点过时,但您可能会发现它很有趣/有用。)