2.2. SIGNED NUMBER REPRESENTATIONS
represented as mark on thermometer, and one need to add a second addend, and it’s positive, we just
shift mark up on thermometer by the value of second addend. If the second addend is negative, then we
shift mark down to absolute value of the second addend.
Addition of two negative numbers works as follows. For example, we need to add -2 and -3 using 16-bit
registers. -2 and -3 is 0xfffe and 0xfffd respectively. If we add these numbers as unsigned, we will get
0xfffe+0xfffd=0x1fffb. But we work on 16-bitregisters, so the resultiscut off, the first 1is dropped, 0xfffb
is left, and this is -5. This works because -2 (or 0xfffe) can be represented using plain English like this: “2
lacks in this value up to maximal value in 16-bit register + 1”. -3 can be represented as “...3 lacks in this
value up to ...”. Maximal value of 16-bit register + 1 is 0x10000. During addition of two numbers and
cutting offby 216 modulo,2 + 3 = 5will be lacking.
2.2.1 Using IMUL over MUL.
Example like listing.3.21.2where two unsigned values are multiplied compiles into listing.3.21.2where
IMULis used instead ofMUL.
This is important property of bothMULandIMULinstructions. First of all, they both produce 64-bit value
if two 32-bit values are multiplied, or 128-bit value if two 64-bit values are multiplied (biggest possible
productin 32-bit environment is
0xffffffff*0xffffffff=0xfffffffe00000001). ButC/C++standardshavenowaytoaccesshigherhalf
of result, and aproductalways has the same size as multiplicands. And bothMULandIMULinstructions
works in the same way if higher half is ignored, i.e., they both generate the same lower half. This is
important property of “two’s complement” way of representing signed numbers.
So C/C++ compiler can use any of these instructions.
ButIMULis more versatile thanMULbecause it can take any register(s) as source, whileMULrequires one
of multiplicands stored inAX/EAX/RAXregister. Even more than that:MULstores result inEDX:EAXpair in
32-bit environment, orRDX:RAXin 64-bit one, so it always calculates the whole result. On contrary, it’s
possible to set a single destination register while usingIMULinstead of pair, and thenCPUwill calculate
only lower half, which works faster [see Torborn Granlund,Instruction latencies and throughput for AMD
and Intel x86 processors^14 ).
Given than, C/C++ compilers may generateIMULinstruction more often thenMUL.
Nevertheless, using compiler intrinsic, it’s still possible to do unsigned multiplication and getfullresult.
This is sometimes calledextended multiplication. MSVC has intrinsic for this called__emul^15 and another
one:_umul128^16. GCC offerint128data type, and if 64-bit multiplicands are first promoted to 128-bit
ones, then aproductis stored into anotherint128value, then result is shifted by 64 bits right, you’ll get
higher half of result^17.
MulDiv() function in Windows
Windows has MulDiv() function^18 , fused multiply/divide function, it multiplies two 32-bit integers into
intermediate 64-bit value and then divides it by a third 32-bit integer. It is easier than to use two compiler
intrinsic, soMicrosoftdevelopersmadeaspecialfunctionforit. Anditseems, thisisbusyfunction, judging
by its usage.
2.2.2 Couple of additions about two’s complement form.
Exercise 2-1. Write a program to determine
the ranges ofchar,short,int, andlong
variables, bothsignedandunsigned, by
printing appropriate values from standard
headers and by direct computation.
Brian W. Kernighan, Dennis M. Ritchie,The C
Programming Language, 2ed, (1988)
(^14) [http://yurichev.com/mirrors/x86-timing.pdf]](http://yurichev.com/mirrors/x86-timing.pdf])
(^15) https://msdn.microsoft.com/en-us/library/d2s81xt0(v=vs.80).aspx
(^16) https://msdn.microsoft.com/library/3dayytw9%28v=vs.100%29.aspx
(^17) Example:http://stackoverflow.com/a/13187798
(^18) https://msdn.microsoft.com/en-us/library/windows/desktop/aa383718(v=vs.85).aspx