7

I have found out that both mul and imul can be used to multiply a signed number to an unsigned number.

For example:

global _start

section .data
    byteVariable DB -5

section .text
_start:

mov al, 2
imul BYTE [byteVariable]

You can replace imul with mul, and the result would still be the same (-10).

Are mul and imul exactly the same when multiplying a signed number to an unsigned number, or is there a difference between them?

Fifoernik
  • 9,779
  • 1
  • 21
  • 27
  • 3
    No, the result is not the same, presumably you just checked the low half of the result only and that is the same. The unsigned `mul` should produce `502` as result in `ax`. – Jester Aug 03 '17 at 23:09
  • @Jester You are right, `ax` is `0x01F6` (`502`) when using `mul`, and `0xFFF6` (`-10`) when using `imul`. –  Aug 03 '17 at 23:46
  • 1
    Always use `imul reg, r/m32` or `imul reg, r/m32, imm` if you don't need the high-half result; it's more efficient on modern CPUs (1 uop) because it doesn't have to write the high half anywhere. https://agner.org/optimize/ – Peter Cordes Jan 18 '20 at 04:17
  • Related: [Why is imul used for multiplying unsigned numbers?](https://stackoverflow.com/q/42587607/8528014) – janw Nov 16 '22 at 16:35

3 Answers3

4

The upper half is different, as mentioned in the comments. If you don't care about the upper half, you can use either mul or imul, in all of their forms (the one-operand forms produce the upper half, but in this scenario you would ignore it).

If you do care about the upper half, neither mul nor imul works by itself, since they just multiply unsigned*unsigned and signed*signed, but you can fix it fairly easily.

Consider that a signed byte has the bit-weights -128, 64, 32, 16, 8, 4, 2, 1 while an unsigned byte has the bit-weights +​128, 64, 32, 16, 8, 4, 2, 1. So you can represent the unsigned value of x in signed format (I know this is confusing but it's the best I can do) as x + 256 x_7 (where x_7 is bit 7 of x). The easiest way to see is probably to split it: x + 2 * 128 * x_7. What's happening here is compensating for the -128 weight, first removing it by adding the value of bit 7 128 times and then going all the way up to the +128 weight by doing it again, of course this can be done in one step.

Anyway, multiplying that by some signed number y and working it out gives 256 x_7 y + xy, where xy is the (double-width) result of imul and 256 x_7 y means "add y to the upper half if the sign of x is set", so a possible implementation is (not tested)

; al has some unsigned value
mov dl, al
sar dl, 7
and dl, [signedByte]
imul BYTE [signedByte]
add ah, dl

Naturally you could sign-extend one operand, zero-extend the other, and use a 16 bit multiplication (any, since the upper half is not relevant this way).

harold
  • 61,398
  • 6
  • 86
  • 164
1

x86 does have an instruction that multiplies signed bytes by unsigned bytes: SSSE3 pmaddubsw.

You can think of it as sign-extending one operand to 16 bits, zero-extending the other to 16 bits, then doing an NxN -> N-bit multiply. (For each SIMD element).

It also horizontally adds pairs of word products from adjacent bytes, but if you unpack the inputs with zeros (punpcklbw or pmovzxbw) then you can get each product separately.

Of course if you have SSE4.1 then you could just pmovsxbw one input and pmovzxbw the other input to feed a regular 16-bit pmullw, if you don't want pairs added.


But if you just want one scalar result, movsx / movzx to feed a regular non-widening imul reg, reg is your best bet.

As Harold points out, mul r/m and imul r/m widening multiplies treat both their inputs the same way so neither can work (unless the signed input is known to be non-negative, or the unsigned input is known to not have its high bit set, so you can treat them both the same after all.)

mul and imul also set FLAGS differently: CF=OF= whether or not the full result fits in the low half. (i.e. the full result is the zero-extension or sign-extension of the low half). For imul reg,r/m or imul reg, r/m, imm, the "low half" is the destination reg; the high half isn't written anywhere.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
-1

Another behavior of flags. For MUL: OF=CF=1 when carry bit changes upper half; For IMUL: OF=CF=1 when carry bit changes sign bit in low part (or just sign bit in result for 2 or 3 operands form)

dim00
  • 1
  • That's not a very clear description. Intel's manual explains it better: [`mul`](https://www.felixcloutier.com/x86/mul) sets CF and OF if the upper half is non-zero. [`imul`](https://www.felixcloutier.com/x86/imul) sets CF and OF if the upper half isn't the sign-extension of the low half. Otherwise they're cleared. So in both cases CF=OF = whether or not the result fits in the low half. (The 2 and 3 operand forms of `imul` *only* produce the low half of the full multiply, but flags are still set the same as the one-operand form.) – Peter Cordes Jan 18 '20 at 04:15