assembly 如何手动计算imul -1 * 3?

hmmo2u0o  于 2023-06-06  发布在  其他
关注(0)|答案(3)|浏览(167)

我正在学习x86-64汇编与在线课程和课程是可怕的不清楚的细节。我已经在网上搜索并阅读了几个SO问题,但无法得到答案。
我试图弄清楚如何手工计算二进制乘法,但我被imul卡住了。
给定这个二进制乘法的例子,11111111 * 00000011,它可以被视为255 * 3无符号或-1 * 3有符号。
255*3

mov al, 255
   mov bl, 3
   mul bl

这很简单,下面是我如何手工计算的,就像十进制乘法一样:

11111111
x    00000011 
--------------
     11111111       
    11111111
--------------
   1011111101

结果溢出,上半部分是ah中的10,下半部分是al中的11111101。我的手工计算与程序结果相符。
-1*3
当涉及到签名时,

mov al, -1
   mov bl, 3
   imul bl

编程结果是ah中的11111111al中的11111101
我怎样才能手工计算出这个结果呢?有人告诉我imul中涉及到符号扩展,但我真的不知道它在这里是如何工作的。
我使用SASM IDE和NASM汇编。

voase2hg

voase2hg1#

n x n => 2n产品的low n bits不依赖于有符号与没有签名,但高半部分有结果相当于将两个输入都符号扩展(imul)或零扩展(mul)到目标宽度,然后进行非加宽乘法,尽管实际实现当然会做一些更有效的事情。看看Erik的答案,看看它在比特方面是什么样子。

如果我只是想手动计算imul会产生什么,我会执行-1 * 3 = -3,然后将-3的位模式计算为2的补码16位整数。因为它是imul,我知道我必须将8位位模式解释为有符号-1,而不是像mul那样的无符号0xff

我不会手动使用二进制,除非我正在检查一些使用乘法来移位和添加位字段的bithack(例如 Popcount assembly / sum indexes of set bits,但即使如此,我实际上也在考虑十六进制或字节。
在C语言中,imul r/m8int16_t ax = (int16_t)(int8_t)al * (int16_t)(int8_t)src;。(C语言很奇怪,因为像*这样的运算符已经将窄输入提升到int,而int至少和int16_t一样宽。
在asm术语中,movsx ax, al(或cbw); movsx cx, byte src ; imul ax, cx,但不破坏CX。(如果你愿意,可以想象一个隐藏的临时寄存器。)
您实际上很少需要扩展mulimul,因为它们在16位或更宽的输入上运行时超过1 uop(因为它们必须将乘积的一半写入多个寄存器,如EDX:EAX,而不仅仅是RAX的低16位)。有趣的是,8位imul bl在现代CPU上作为单个uop运行。参见https://uops.info/https://agner.org/optimize/
但是imul bl只将其结果扩展到16位,而不是一直扩展到方便的32位,因此您通常希望将imul ecx, ebx的输入扩展到32位,因为32-bit is the most efficient operand-size most of the time on x86-64imul r, r/mimul r, r/m, imm形式是单微操作,在现代CPU上速度很快。对于64位imul,一些较旧或低功耗的CPU速度较慢。

qqrboqgw

qqrboqgw2#

技术上讲,你在长乘法上走了捷径。虽然这种捷径对于获得无符号位模式是相当合理的,但它在概念上掩盖了一些东西。

11111111
x    00000011 
--------------
     11111111
    11111111
--------------
   1011111101

对于未签名的,我们有:

11111111
    x    00000011
    --------------
 0000000011111111
+000000011111111 
+00000000000000 
+0000000000000 
+000000000000 
+00000000000 
+0000000000 
+000000000 
------------------   
00000001011111101  = 255 x 3 = 765,

它们的总和是17位,尽管从mul中只能得到16位,并且进位/无符号溢出不会发生,因为输入只有8位宽。
在上面的例子中,我们添加了前导零,但将尾随零保留为空白。
而在有符号算术中:

11111111
    x    00000011
    --------------
 1111111111111111
+111111111111111 
+00000000000000 
+0000000000000 
+000000000000 
+00000000000 
+0000000000 
-000000000         // note, subtracting because MSB has place value -2^7
------------------   
11111111111111101  = -1 x 3 = -3, the summation being 17 bits

它们的总和是17位,尽管从imul中只能得到16位,并且不会发生有符号溢出,因为输入只有8位宽。
因此,我们通过将被乘数(第一个操作数)的符号扩展为16位部分乘积来说明被乘数的符号。
我们通过减去而不是添加最高有效部分乘积来说明乘数(第二个操作数)的符号。因为这是乘以0或-2^7,即该输入中符号位的位值。对于unsigned,所有的位值都是正的,但是n位2's complement的工作方式是给MSB一个位值-2^(n-1)而不是+2^(n-1)。例如,0x80-128在8位2的补码(int8_t)中的位模式,但+128作为无符号(uint8_t)。
或者,我们可以对输入进行符号扩展,并做16个部分积而不是8个,但这需要更多的工作。

o7jaxewo

o7jaxewo3#

老实说,我不能完全理解其他两个答案。对我来说太复杂了。我只需要一个愚蠢的,简单的,通用的规则。
我只想选适合我的。
其结果相当于将两个输入都符号扩展(imul)或零扩展(mul)到目标宽度,然后执行非加宽乘法
@Peter Cordes
要手动计算,有几种方法:您可以将两个输入符号扩展到16位,并执行16 × 16操作,仅保留低16位
@埃里克·艾德
我试过了,验证过了,这条规则对我很有效。
-1*3
符号扩展

11111111 11111111
 x00000000 00000011
-------------------
  11111111 11111111
 111111111 1111111
-------------------
1011111111 11111101

保持低16位,我得到正确的结果11111111(ah) 11111101(al)
尝试一个4位示例:
-2 * 31110 imul 0011
符号扩展

1111 1110
 x0000 0011
-----------
  1111 1110
 11111 110
-----------
101111 1010

保留低8位,结果为1111 1010-6
以前我不确定sign extended在imul中是如何工作的,现在我明白了,很容易验证。顺便说一句,如果你发现手动计算对一些例子来说很乏味(例如4位-7 * -11001 x 1111、符号扩展1111 1001 x 1111 1111,需要添加许多行),您可以使用Windows计算器(程序员模式)并快速验证结果。

相关问题