现代CPU可以在两个原生大小的单词之间执行扩展乘法,并将低和高结果存储在单独的寄存器中。类似地,在执行除法时,它们将商和余数存储在两个不同的寄存器中,而不是丢弃不需要的部分。
是否存在某种可移植的gcc内在函数,它将采用以下签名:
void extmul(size_t a, size_t b, size_t *lo, size_t *hi);
或类似的东西,以及分裂:
void extdiv(size_t a, size_t b, size_t *q, size_t *r);
我知道我可以通过在代码中输入#ifdef来使用内联汇编和shoehorn可移植性来实现它,或者我可以使用部分和来模拟乘法部分(这将显着更慢)但我想避免这样做为了便于阅读。当然有一些内置函数可以做到这一点吗?
答案 0 :(得分:17)
对于4.6版本的gcc,您可以使用__int128
。这适用于大多数64位硬件。例如
要获得64位64位乘法的128位结果,只需使用
void extmul(size_t a, size_t b, size_t *lo, size_t *hi) {
__int128 result = (__int128)a * (__int128)b;
*lo = (size_t)result;
*hi = result >> 64;
}
在x86_64上,gcc非常聪明,可以将其编译为
0: 48 89 f8 mov %rdi,%rax
3: 49 89 d0 mov %rdx,%r8
6: 48 f7 e6 mul %rsi
9: 49 89 00 mov %rax,(%r8)
c: 48 89 11 mov %rdx,(%rcx)
f: c3 retq
不需要本机128位支持或类似内容,并且在内联后仅保留mul
指令。
编辑:在32位拱形上,它的工作方式类似,您需要将__int128_t
替换为uint64_t
,将移位宽度替换为32.优化将适用于更旧的gcc。
答案 1 :(得分:3)
对于那些想知道问题的另一半(除法)的人,gcc并未为此提供内在的信息,因为处理器除法指令不符合标准。
对于64位x86目标上的128位除数和32位x86目标上的64位除数都是如此。问题在于,在标准要求结果被截断的情况下,DIV将导致除法溢出异常。例如(unsigned long long) (((unsigned _int128) 1 << 64) / 1)
的计算结果应为0,但如果使用DIV进行计算,则会导致除法溢出异常。
(感谢@ross-ridge提供此信息)