我写一个函数库提供所有传统的运算符和函数的签署整数类型s0128
, s0256
, s0512
, s1024
和浮点类型f0128
, f0256
, f0512
, f1024
。
我写了s0128
, s0256
, s0512
, s1024
乘套路了,但我得到的是迷惑我错误的结果。 我以为我可以级联与64位乘法imul rcx
指令(即产生128位结果在rdx:rax
)以同样的方式,我可以做到与无符号运算数相同mul rcx
指令......但答案与imul
是错误的。
我怀疑有一些技巧,使这项工作,也许混合imul
和mul
指令-什么的。 或者是有一些原因,一个不能执行已签署的乘法指令较大的乘?
所以,你懂技术,我将描述最小的版本, s0128
操作数。
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
每当码相乘两个64位的值,它生成一个128位结果edx:eax
。 各码生成一个128位的结果时,它总结该结果到一个累积三重与64位寄存器addq
, adcq
, adcq
指令(其中最终adcq
指令只增加零,以保证任何进位标志被传播)。
当我通过小的正数乘以小负数作为试验,结果是否定的,但也有在128位上的64位值中的底部一个或两个非零位s0128
结果。 这意味着我的东西是不完全正确,在多倍签署成倍繁殖。
当然级联是更广泛的相当多的s0256
, s0512
, s1024
。
我在想什么? 我必须两个操作数转换为无符号,进行无符号乘法,然后否定的结果,如果一个操作数(但不能同时)为阴性? 或者,我可以计算多倍结果与imul
签署乘法指令?
当你建立多重退出小成倍的扩展精度,你结束了符号和无符号运算的混合物。
特别是,如果你在半打破符号值,你当作签署的上半部,而下半部为无符号。 这同样适用于扩展精度此外真实的,其实。
考虑该任意例如,在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代码演示了一个32×32的乘法中的16×16乘法方面实现。 相同的原理构建的128×128乘出的64×64乘法运算时适用。
#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,你不会有处理多重签名。)
该组件宝石显示如何有效地否定扩展精度值。 (该宝石页面是有点过时,但你会发现它很有趣/有用)。