why X86 provides pair of division and multiply instructions? - c

I noticed that, unsigned int and int shared the same instruction for addition and subtract. But provides idivl / imull for integer division and mutiply, divl / mull for unsigned int . May I know the underlying reason for this ?

The results are different when you multiply or divide, depending on whether your arguments are signed or unsigned.
It's really the magic of two's complement that allows us to use the same operation for signed and unsigned addition and subtraction. This is not true in other representations -- ones' complement and sign-magnitude both use a different addition and subtraction algorithm than unsigned arithmetic does.
For example, with 32-bit words, -1 is represented by 0xffffffff. Squaring this, you get different results for signed and unsigned versions:
Signed: -1 * -1 = 1 = 0x00000000 00000001
Unsigned: 0xffffffff * 0xffffffff = 0xfffffffe 00000001
Note that the low word of the result is the same. On processors that don't give you the high bits, there is only one multiplication instruction necessary. On PPC, there are three multiplication instructions — one for the low bits, and two for the high bits depending on whether the operands are signed or unsigned.

Most microprocessors implement multiplication and division with shift-and-add algorithm (or a similar algorithm. This of course requires that the sign of the operands be handled separately.
While implementing multiplication and divisions with add-an-substract would have allowed to not worry about sign and hence allowed to handle signed vs. unsigned integer values interchangeably, it is much less efficient algorithm and that's likely why it wasn't used.
I just read that some modern CPUs use alternatively the Booth encoding method, but that algorithm also implies asserting the sign of the values.

In x86 sign store in high bit of word (if will talk about integer and unsigned integer)
ADD and SUB command use one algorithm for signed and unsigned in - it get correct result in both.
For MULL and DIV this is not worked. And you should "tell" to CPU what int you want "use" signed or unsigned.
For unsigned use MULL and DIV. It just operate words - it is fast.
For signed use MULL and IDIV. It get word to absolute (positive) value, store sign for result and then make operation. This is slower than MULL and DIV.

Related

Does C uses 2's complement internally to evaluate unsigned numbers arithmetic like 5-4?

I have C code as
#include<stdio.h>
int main()
{
unsigned int a = 5;
unsigned int b = 4;
printf("%u",a-b);
}
Output of above code is 1, I am thinking that C has calculated internally the result as taking 2's compliment of -4 and then using compliment arithmetic to evaluate the result. Please correct me if anything I am interpreting wrong. (Here, I am talking about how C actually calculates result using binary)
Does C uses 2's complement internally to evaluate unsigned numbers arithmetic like 5-4?
No, for two reasons.
unsigned int a = 5, b = 4;
printf("%u",a-b);
C guarantees that arithmetic on unsigned integer types is performed modulo the size of the type. So if you computed b-a, you'd get -1 which would wrap around to UINT_MAX, which is probably either 65535 or 4294967295 on your machine. But if you compute a-b, that's just an ordinary subtraction that doesn't overflow or underflow in any way, so the result is an uncomplicated 1 without worrying about 2's complement or modulo arithmetic or anything.
If your compiler, or your CPU architecture, chooses to implement a - b as a + -b, that's their choice, but it's an implementation detail way beneath the visibility of the C Standard, or ordinary programmers like you and me, and it won't affect the observable results of a C program at all.
Where things get interesting, of course, is with addition and subtraction of signed quantities. Up above I said that under unsigned arithmetic, 4 - 5 is -1 which wraps around to UINT_MAX. Using signed arithmetic, of course, 4 - 5 is -1 which is -1. Under 2's complement arithmetic, it Just So Happens that the bit patterns for -1 and UINT_MAX are identical (typically 0xffff or 0xffffffff), and this is why 2's complement arithmetic is popular, because your processor gets to define, and your C compiler gets to use, just one set of add and sub instructions, that work equally well for doing signed and unsigned arithmetic. But (today, at least), C does not mandate 2's complement arithmetic, and that's the other reason why the answer to your original question is "no".
But to be clear (and to go back to your question): Just about any C compiler, for just about any architecture, is going to implement a - b by emitting some kind of a sub instruction. Whether the processor then chooses to implement sub as a two's complement negate-and-add, or some other kind of negate-and-add, or via dedicated bitwise subtraction-with-borrow logic, is entirely up to that processor, and it doesn't matter (and is probably invisible) as long as it always returns a mathematically appropriate result.
The arithmetic method generally whatever is most natural on the target hardware. It is not defined by the C language.
When a processor's ALU has at least int sized registers, a-b will not doubt be implemented as a single SUB instruction (or whatever the target's op-code mnemonic for subtraction might be). In the hardware logic it may well be that the logic is equivalent to a + (~b + 1) (i.e. 2's complement the RHS and add) - but that is a hardware logic/micro-code implementation issue, not a language or compiler behaviour.
At Godbolt for GCC x86 64-bit, the statement:
unsigned int c = a - b ;
generates the following assembly code (my comments):
mov eax, DWORD PTR [rbp-4] ; Load a to EAX
sub eax, DWORD PTR [rbp-8] ; Subtract b from EAX
mov DWORD PTR [rbp-12], eax ; Write result to c
So in that sense your question is not valid - C does not do anything in particular, the processor performs the subtraction intrinsically.
The C standard allows 2's, 1's and sign+magnitude arithmetic, but in practice the world has settled on 2's complement and machines that use other representations are arcane antiques that probably never had C compilers targeted for them in any case.
There are in any case moves to remove the option for anything other than 2's complement in the language: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2218.htm

Signed Multiplication of 1024-bit 2's complement numbers using 32-bit chunks

So I have the following struct definition for my 1024-bit number (I want to use 2's complement representation here and I am on a 32-bit system):
typedef struct int1024
{
int32_t num[32]; //should I use uint32_t?
} int1024;
Basically an array that holds the segments of the number.
For add since, signed and unsigned addition are the same. I can simply use the instructions add and adc to carry out the extended operation for my bigger array.
Now for multiplication. I was able to get an unsigned multiplication to work using a combination of imul (using the upper and lower results) and adc using the classic O(n^2) multiplication algorithm. However I need signed multiplication for my results. I know people say just to use the signed magnitude version. i.e. take the 2's complement when negative and at the end apply 2's complement if needed. But all the extra noting and adding 1 will be really expensive, since I need to do a lot of multiplications. Is there a technique for doing large signed multiplication for number representations like this. (I don't really want any branching or recursive calls here). I guess one of my main problems is how to deal with carry and the high part of the multiplication of negative numbers. (just an idea, maybe I could use a combination of signed and unsigned multiplication, but I don't know where to begin). If you don't have time for 1024 bits, a 128 bit answer will suffice (I will just generalize it).
Note that in a 1024-bit integer only the very top bit, bit 1023, is the sign bit and has a place-value of -(2**1023). All the other bits have their normal +(2**pos) place value. i.e. the lower "limbs" all need to be treated as unsigned when you do widening multiplies on them.
You have one sign bit and 1023 lower bits. Not one sign bit per limb.
Also, the difference between signed and unsigned multiply is only in the high half of a widening multiply (N x N => 2N bits). That's why x86 has separate instructions for imul r/m64 and mul r/m64 to do a full-multiply into RDX:RAX (https://www.felixcloutier.com/x86/imul vs. https://www.felixcloutier.com/x86/mul). But for non-widening there's only imul r, r/m and imul r, r/m, imm which compilers use for both unsigned and signed (and so should humans).
Since you want a 1024x1024 => 1024-bit product which discards the upper 1024 bits, you can and should just make your whole thing unsigned.
When you're writing in asm, you can use whatever chunk size you can, e.g. 64-bit in 64-bit mode, not limiting yourself to the C chunk size.
See https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/ia-large-integer-arithmetic-paper.pdf for how to use BMI2 mulx (leaves FLAGS untouched so doesn't disturb adc chains, and only has RDX as an implicit input, with other source and both outputs being explicit, saving on MOV instructions). And optionally also Broadwell ADOX / ADCX to run two dep chains in parallel to get more ILP for medium sized BigInteger stuff like 512x512-bit or 512x64-bit (which they use as an example).

Does signed to unsigned casting in C changes the bit values

I've done some quick tests that a signed int to unsigned int cast in C does not change the bit values (on an online debugger).
What I want to know is whether it is guaranteed by a C standard or just the common (but not 100% sure) behaviour ?
Conversion from signed int to unsigned int does not change the bit representation in two’s-complement C implementations, which are the most common, but will change the bit representation for negative numbers, including possible negative zeroes on one’s complement or sign-and-magnitude systems.
This is because the cast (unsigned int) a is not defined to retain the bits but the result is the positive remainder of dividing a by UINT_MAX + 1 (or as the C standard (C11 6.3.1.3p2) says,
the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.
The two’s complement representation for negative numbers is the most commonly used representation for signed numbers exactly because it has this property of negative value n mapping to the same bit pattern as the mathematical value n + UINT_MAX + 1 – it makes it possible to use the same machine instruction for signed and unsigned addition, and the negative numbers will work because of wraparound.
Casting from a signed to an unsigned integer is required to generate the correct arithmetic result (the same number), modulo the size of the unsigned integer, so to speak. That is, after
int i = anything;
unsigned int u = (unsigned int)i;
and on a machine with 32-bit ints, the requirement is that u is equal to i, modulo 232.
(We could also try to say that u receives the value i % 0x100000000, except it turns out that's not quite right, because the C rules say that when you divide a negative integer by a positive integer, you get a quotient rounded towards 0 and a negative remainder, which isn't the kind of modulus we want here.)
If i is 0 or positive, it's not hard to see that u will have the same bit pattern.
If i is negative, and if you're on a 2's complement machine, it turns out the result is also guaranteed to have the same bit pattern. (I'd love to present a nice proof of that result here, but I don't have time just now to try to construct it.)
The vast majority of today's machines use 2's complement. But if you were on a 1's complement or sign/magnitude machine, I'm pretty sure the bit patterns would not always be the same.
So, bottom line, the sameness of the bit patterns is not guaranteed by the C Standard, but arises due to a combination of the C Standard's requirements, and the particulars of 2's complement arithmetic.

Bitwise operations on negative numbers

I was reading a book on C, where in some section it says: " The bitwise operations are typically used with unsigned types.".
Question: why?
Simply because it is not immediately clear what bit operations on the sign bit of a signed number should mean.
Unsigned types have no special bits, everything works straight
forward.
Signed types have a special sign bit and can interpret that with
three different encodings to represent negative values (ones and two's complement or sign and magnitude).
From the programming languages' point of view the unsigned does not mean, that it cannot be negative. It means, that the first bit of the number is not used for determining if the value is negative or positive.
So, for an 8 bit value the bitwise operators consider all the 8 bits, thus working on the range 0..255 (while the mathematical operators can consider the first bit as being the signedness indicator, thus working on a range of -128 to +127).
Unsigned operands do not have any special bits for sign representation.
For signed operands, the special bit for sign representation may interrupt operations which we want to perform.
Bitwise operations are typically used with unsigned types or signed types with non-negative values.
The C Standard supports 3 different representations for negative values: applying bitwise operators on such values has at best an implementation defined behavior.
For example -1 is represented:
as |1000|0000|0000|0001| on a 16-bit system with sign magnitude.
as |1111|1111|1111|1110| on a 16-bit system with one's complement.
and more commonly as |1111|1111|1111|1111| on a 16-bit system with two's complement.
As a consequence, -1 & 3 can have 3 different values:
1 on sign-magnitude systems,
2 on one's complement systems,
3 on two's complement systems,
Other operations may behave in even more unexpected ways on negative numbers, such as << and >>, especially on the DS9K.
Conversely, the bitwise operations are fully defined on unsigned types, which are mandated to have binary representation (with some restrictions on the right operand of shift operators).

negative integer number >> 31 = -1 not 1? [duplicate]

This question already has answers here:
Arithmetic bit-shift on a signed integer
(6 answers)
Closed 9 years ago.
so, lets say I have a signed integer (couple of examples):
-1101363339 = 10111110 01011010 10000111 01110101 in binary.
-2147463094 = 10000000 00000000 01010000 01001010 in binary.
-20552 = 11111111 11111111 10101111 10111000 in binary.
now: -1101363339 >> 31 for example, should equal 1 right? but on my computer, I am getting -1. Regardless of what negative integer I pick if x = negative number, x >> 31 = -1. why? clearly in binary it should be 1.
Per C99 6.5.7 Bitwise shift operators:
If E1 has a signed type and a negative value, the resulting value is implementation-defined.
where E1 is the left-hand side of the shift expression. So it depends on your compiler what you'll get.
In most languages when you shift to the right it does an arithmetic shift, meaning it preserves the most significant bit. Therefore in your case you have all 1's in binary, which is -1 in decimal. If you use an unsigned int you will get the result you are looking for.
Per C 2011 6.5.7 Bitwise shift operators:
The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type
or if E1 has a signed type and a nonnegative value, the value of the result is the integral
part of the quotient of E1/ 2E2. If E1 has a signed type and a negative value, the
resulting value is implementation-defined.
Basically, the right-shift of a negative signed integer is implementation defined but most implementations choose to do it as an arithmetic shift.
The behavior you are seeing is called an arithmetic shift which is when right shifting extends the sign bit. This means that the MSBs will carry the same value as the original sign bit. In other words, a negative number will always be negative after a left shift operation.
Note that this behavior is implementation defined and cannot be guaranteed with a different compiler.
What you are seeing is an arithmetic shift, in contrast to the bitwise shift you were expecting; i.e., the compiler, instead of "brutally" shifting the bits, is propagating the sign bit, thus dividing by 2N.
When talking about unsigned ints and positive ints, a right shift is a very simple operation - the bits are shifted to the right by one place (inserting 0 on the left), regardless of their meaning. In such cases, the operation is equivalent to dividing by 2N (and actually the C standard defines it like that).
The distinction comes up when talking about negative numbers. Several negative numbers representation exist, although currently for integers most commonly 2's complement representation is used.
The problem of a "brutal" bitwise shift here is, for starters, that one of the bits is used in some way to express the sign; thus, shifting the binary digits regardless of the negative integers representation can give unexpected results.
For example, commonly in 2's representation the most significant bit is 1 for negative numbers, 0 for positive numbers; applying a bitwise shift (with zeroes inserted to the left) to a negative number would (between other things) make it positive, not resulting in the (usually expected) division by 2N
So, arithmetic shift is introduced; negative numbers represented in 2's complement have an interesting property: the division by 2N behavior of the shift is preserved if, instead of inserting zeroes from the left, you insert bits that have the same value of the original sign bit.
In this way, signed divisions by 2N can be performed with just a bit of extra logic in the shift, without having to resort to a fully-fledged division routine.
Now, is arithmetic shift guaranteed for signed integers? In some languages yes1, but in C it's not like that - the behavior of the shift operators when dealing with negative integers is left as an implementation-defined detail.
As often happens, this is due to different hardware support for the operation; C is used on vastly different platforms, and, especially in the past, there was quite a difference in the "cost" of operations depending on the platform.
For example, if the processor does not provide an arithmetic right shift instruction, the compiler would be mandated to emit a much slower DIV instruction of some kind, which could be a problem in an inner loop on slower processors. For these reasons, the C standard leaves it up to the implementor to do the most appropriate thing for the current platform.
In your case, your implementation probably chose arithmetic shift because you are running on an x86 processor, that uses 2's complement arithmetic and provides both bitwise and arithmetic shift as single CPU instructions.
Actually, languages like Java even have separated arithmetic and bitwise shift operators - this is mainly due to the fact that they do not have unsigned types to e.g. store bitfields.

Resources