C language has signed and unsigned types like char and int. I am not sure, how it is implemented on assembly level, for example it seems to me that multiplication of signed and unsigned would bring different results, so do assembly do both unsigned and signed arithmetic or only one and this is in some way emulated for the different case?
-
I would suggest using `
` on C99 – Basile Starynkevitch Aug 11 '14 at 10:55 -
1Signed and unsigned multiply only have different results if you mean the version that isn't supported by C - the one where the result is double the width of the operands. – harold Aug 11 '14 at 10:59
-
isnt supported by c? what do you mean? – user2214913 Aug 11 '14 at 11:08
-
3In 2's complement, there's no different in addition, subtraction and non-widening multiplication for signed and unsigned numbers http://stackoverflow.com/questions/14063599/why-are-signed-and-unsigned-multiplication-different-instructions-on-x86-64 – phuclv Aug 12 '14 at 17:47
-
Related: [Which 2's complement integer operations can be used without zeroing high bits in the inputs, if only the low part of the result is wanted?](https://stackoverflow.com/questions/34377711/which-2s-complement-integer-operations-can-be-used-without-zeroing-high-bits-in). Generally anything where high bits don't affect the low part of the result also means that sign bits aren't special and signed vs. unsigned is the same bitwise operation (e.g. addition or left shift, but not right shift (arithmetic vs. logical)). – Peter Cordes Oct 03 '17 at 19:10
3 Answers
If you look at the various multiplication instructions of x86, looking only at 32bit variants and ignoring BMI2, you will find these:
imul r/m32
(32x32->64 signed multiply)imul r32, r/m32
(32x32->32 multiply) *imul r32, r/m32, imm
(32x32->32 multiply) *mul r/m32
(32x32->64 unsigned multiply)
Notice that only the "widening" multiply has an unsigned counterpart. The two forms in the middle, marked with an asterisk, are both signed and unsigned multiplication, because for the case where you don't get that extra "upper part", that's the same thing.
The "widening" multiplications have no direct equivalent in C, but compilers can (and often do) use those forms anyway.
For example, if you compile this:
uint32_t test(uint32_t a, uint32_t b)
{
return a * b;
}
int32_t test(int32_t a, int32_t b)
{
return a * b;
}
With GCC or some other relatively reasonable compiler, you'd get something like this:
test(unsigned int, unsigned int):
mov eax, edi
imul eax, esi
ret
test(int, int):
mov eax, edi
imul eax, esi
ret
(actual GCC output with -O1)
So signedness doesn't matter for multiplication (at least not for the kind of multiplication you use in C) and for some other operations, namely:
- addition and subtraction
- bitwise AND, OR, XOR, NOT
- negation
- left shift
- comparing for equality
x86 doesn't offer separate signed/unsigned versions for those, because there's no difference anyway.
But for some operations there is a difference, for example:
- division (
idiv
vsdiv
) - remainder (also
idiv
vsdiv
) - right shift (
sar
vsshr
) (but beware of signed right shift in C) - comparing for bigger than / smaller than
But that last one is special, x86 doesn't have separate versions for signed and unsigned of this either, instead it has one operation (cmp
, which is really just a nondestructive sub
) that does both at once, and gives several results (multiple bits in "the flags" are affected). Later instructions that actually use those flags (branches, conditional moves, setcc
) then choose which flags they care about. So for example,
cmp a, b
jg somewhere
Will go somewhere
if a
is "signed greater than" b
.
cmp a, b
jb somewhere
Would go somewhere
if a
is "unsigned below" b
.
See Assembly - JG/JNLE/JL/JNGE after CMP for more about the flags and branches.
This won't be a formal proof that signed and unsigned multiplication are the same, I'll just try to give you insight into why they should be the same.
Consider 4-bit 2's-complement integers. The weights their individual bits are, from lsb to msb, 1, 2, 4, and -8. When you multiply two of those numbers, you can decompose one of them into 4 parts corresponding to its bits, for example:
0011 (decompose this one to keep it interesting)
0010
---- *
0010 (from the bit with weight 1)
0100 (from the bit with weight 2, so shifted left 1)
---- +
0110
2 * 3 = 6 so everything checks out. That's just regular long multiplication that most people learn in school, only binary, which makes it a lot easier since you don't have to multiply by a decimal digit, you only have to multiply by 0 or 1, and shift.
Anyway, now take a negative number. The weight of the sign bit is -8, so at one point you will make a partial product -8 * something
. A multiplication by 8 is shifting left by 3, so the former lsb is now the msb, and all other bits are 0. Now if you negate that (it was -8 after all, not 8), nothing happens. Zero is obviously unchanged, but so is 8, and in general the number with only the msb set:
-1000 = ~1000 + 1 = 0111 + 1 = 1000
So you've done the same thing you would have done if the weight of the msb was 8 (as in the unsigned case) instead of -8.
-
Im not sure if i understood - does it mean that becouse C do not support 32*32->64 (or does he , like "unsigned64 c = a*b;" wouldnt it work? there is no difference? it still do not answers all the question , or i do not understand the answer - if on assembly level only signed arithmetic or only unsigned arithmetic is supported, fully/partially, or both? 9sorry fot this question is a bit complex) – user2214913 Aug 11 '14 at 15:43
-
2@user2214913 you can write `uint64_t res = a * b` but that results in the *narrow* result, but then widened. Or you could write `uint64_t res = a * (uint64_t)b` and get the wide result, but then you really have a 64x64->64 multiplication where the upper bits of the operands just happen to be zero (but this is one of the things a compiler *might* implement with a 32x32->64 mul in some circumstances). Not really the same thing though. As for assembly supporting (un)signed arithmetic, the point of this answer (and the other answer) is to show they're mostly the same thing anyway. – harold Aug 11 '14 at 16:11
-
signed and unsigned doesnt seem the same, for example -200 * -200 (speaking of bytes) will bring the same as unsigned(-200) * unsigned(-200) ? Im a bit lost here. so you say that c do not support widening multiplication of narrow arguments ? are you sure as to that? – user2214913 Aug 12 '14 at 08:56
-
1@user2214913 very sure. Also, I'll show why signed and unsigned multiplication are the same – harold Aug 12 '14 at 09:05
-
alright, most things clear now, pity wide multiply is undefined bahavior in c... Though maybe yet one final doubt maybe someone could answer: as from this all said i understand that on c level signed and unsigned arithmetic are only partially suported (as wide multiply is UB). On assembly level instead in short the whole signed and unsigned arithmetic are supported , or not? (is there some example of something signed/unsigned that is not supported in x86?) tnx for answer – user2214913 Aug 12 '14 at 15:07
-
1@user2214913 conversion between `uint64_t` and `double` is not supported, but the signed version is. I think that's all, as far as I can think of now, everything either has both a signed and an unsigned version or doesn't need both of them. (ignoring vector instructions here) – harold Aug 12 '14 at 15:35
-
2http://stackoverflow.com/a/4040834/995714 "imul is more powerful because it accepts using somewhat arbitrary operand registers, whereas mul necessarily uses eax as one of the inputs, and writes out the result into edx:eax. imul makes it easier for the compiler... since C compilers use imul and not mul, Intel and AMD invest more efforts into optimizing imul than mul, making the former faster in recent processors. This makes imul even more attractive." – phuclv Aug 12 '14 at 17:52
-
I'm curious about `mul` with x86-64. Does `mul` do 64x64 to 128? I mean lower 64-bit word in rda and upper in rdx? – Z boson Mar 02 '15 at 08:54
-
@Zboson they're `rax` and `rdx`, but yes (only the 64bit version of `mul` of course) – harold Mar 02 '15 at 09:55
-
@harold, the 64-bit version of mul returns the upper 64-bit word in `rdx`? That's quite significant for anyone implementing 64x64 to 128 because it's a lot calculations to get the upper word otherwise. – Z boson Mar 02 '15 at 10:10
-
@Zboson it does, there's even a signed version as well (though converting the upper half between signed and unsigned wouldn't be so bad) – harold Mar 02 '15 at 10:40
-
I guess I'm wasting my time with [this](https://stackoverflow.com/questions/28807341/simd-signed-with-unsigned-multiplication-for-64-bit-64-bit-to-128-bit) then? Maybe it would be efficient with AVX512? Would a AVX512 upper word 64b * 64b to 128b alogrithm beat `mul` eight times? – Z boson Mar 02 '15 at 10:43
-
Well I was thinking pre AVX512 as well. I guess I'll have to write it in assembly. Maybe I can compare my result to __int128 with GCC and see if my SIMD method beats __int128. For SSE I have to do about 20 instructions to get 64b*64b to128b twice. I doubt that will beat 64-bit `mul` twice. – Z boson Mar 02 '15 at 10:59
Most of the modern processors support signed and unsigned arithmetic. For those arithmetic which is not supported, we need to emulate the arithmetic.
Quoting from this answer for X86 architecture
Firstly, x86 has native support for the two's complement representation of signed numbers. You can use other representations but this would require more instructions and generally be a waste of processor time.
What do I mean by "native support"? Basically I mean that there are a set of instructions you use for unsigned numbers and another set that you use for signed numbers. Unsigned numbers can sit in the same registers as signed numbers, and indeed you can mix signed and unsigned instructions without worrying the processor. It's up to the compiler (or assembly programmer) to keep track of whether a number is signed or not, and use the appropriate instructions.
Firstly, two's complement numbers have the property that addition and subtraction is just the same as for unsigned numbers. It makes no difference whether the numbers are positive or negative. (So you just go ahead and ADD and SUB your numbers without a worry.)
The differences start to show when it comes to comparisons. x86 has a simple way of differentiating them: above/below indicates an unsigned comparison and greater/less than indicates a signed comparison. (E.g. JAE means "Jump if above or equal" and is unsigned.)
There are also two sets of multiplication and division instructions to deal with signed and unsigned integers.
Lastly: if you want to check for, say, overflow, you would do it differently for signed and for unsigned numbers.

- 1
- 1

- 30,259
- 8
- 73
- 100
-
-
1Mainly 2 points, `C` specs just specify the behaviour of signed and unsigned types. They don't specify how to implement this behaviour. If a processor supports required operations, it is implemented using those, otherwise compiler writer choose to implement the behaviour with available limited instruction set. That is the reason why most of the compiler writers choose 2s complement for negative numbers, because add/subtract on 2s complement remains same as unsigned. On X86, there are different instructions for signed/unsigned multiplication. – Mohit Jain Aug 11 '14 at 15:51
-
signed and unsigned doesnt seem the same, for example -200 * -200 (speaking of bytes) will bring the same as unsigned(-200) * unsigned(-200) ? Im a bit lost here. so you say that c do not support widening multiplication of narrow arguments ? are you sure as to that? – user2214913 Aug 12 '14 at 08:46
-
1Yes the product is same because of following choice. -a becomes (m-a) in unsigned where m is the 2^(CHAR_BITS * sizeof(unsigned int)). unsigned (-a) * unsigned (-a) = (m-a) * (m-a) = m(m-2a) + a*a = a*a (C guarantees wrapping around of unsigned integral types) = -a * -a – Mohit Jain Aug 12 '14 at 09:46
-
if m is 256, -200 would be 256-200 = 56? forgot that -200 is out of scope of byte, should take -100 as example , -100 is 156, -100* -100 would be 10 000, 156 * 156 is 24 336, what with that ? – user2214913 Aug 12 '14 at 15:12
-
1[(156 * 156) mod 256](https://www.google.co.in/search?q=156*156&oq=156*156&aqs=chrome..69i57j0.2673j0j7&client=ubuntu-browser&sourceid=chrome&es_sm=93&ie=UTF-8#q=(100*100)+mod+256) = [(100 * 100) mod 256](https://www.google.co.in/search?q=156*156&oq=156*156&aqs=chrome..69i57j0.2673j0j7&client=ubuntu-browser&sourceid=chrome&es_sm=93&ie=UTF-8#q=(156*156)+mod+256) = 16 – Mohit Jain Aug 12 '14 at 15:44
A little supplement for cmp
and sub
. We know cmp
is considered as non-destructive sub
, so let's focus on sub
.
When a x86 cpu does a sub
instruction, for example,
sub eax, ebx
How does the cpu know if either values of eax or ebx are signed or unsigned? For example, consider a 4 bit width number in two's complement:
eax: 0b0001
ebx: 0b1111
In either signed or unsigned, value of eax will be interpreted as 1(dec)
, which is fine.
However, if ebx is unsigned, it will be interpreted as 15(dec)
, result becomes:
ebx:15(dec) - eax: 1(dec) = 14(dec) = 0b1110 (two's complement)
If ebx is signed, then results becomes:
ebx: -1(dec) - eax: 1(dec) = -2(dec) = 0b1110 (two's complement)
Even though for both signed or unsigned, the encode of their results in two's complement are same: 0b1110
.
But one is positive: 14(dec), the other is negative: -2(dec), then comes back our question: how does the cpu tell which to which?
The answer is the cpu will evaluate both, from: http://x86.renejeschke.de/html/file_module_x86_id_308.html
It evaluates the result for both signed and unsigned integer operands and sets the OF and CF flags to indicate an overflow in the signed or unsigned result, respectively. The SF flag indicates the sign of the signed result.
For this specific example, when the cpu sees the result: 0b1110
, it will set the SF flag to 1
, because it's -2(dec)
if 0b1110
is interpreted as a negative number.
Then it depends on the following instructions if they need to use the SF flag or simply ignore it.

- 65
- 6
-
http://teaching.idallen.com/dat2343/10f/notes/040_overflow.txt also has 4-bit examples and says more about signed overflow vs. unsigned carry, and how those flags are set. – Peter Cordes Oct 02 '17 at 18:31
-