signed values implicitly cast to unsiged → even for comparison operators
- -1 > 0U because -1 cast to unsigned ist umax.
Common mistake is passing a signed int into a function that expects unsigned → memory leak or vuln.
Sign extension:
- to extend from w to w+k bits, make k copies of the sign bit (MSB).
- two’s complement works that way → we add -127 (or whatever the minus is) + … those bits.
- by induction → extending by 1 does since the top 1 is now a large +
- this gives me .
unsigned is always zero extension!
Unsigned: Adding numbers is always with carry truncated → in C at least (in assembly, you can recover carry flag)
- addition in C is an abelian group (since it’s mod addition)
Signed: same carry truncation happens (int) ((uint) u + (uint) v) == u + vsame thing!- you can overflow and underflow
- this happens when there is not carry into the MSB and it’s suddenly positive
Multipliation:
- Unsigned:
- we need twice the number of bits after multiplication
- discard the top bits
- for w-long ints
- it’s a commutative ring with unsigned addition
- Signed also discards the top half
- the lower half is exactly the same as the unsigned mult!
- it works with the - * + case as well, it automatically gives you the MSB bit set
Multiplication is much cheaper than division → we can shift and multiply with special numbers to divide, which is much faster! (Note, this mostly only works on unsigned numbers!!!)
- shifting is multiplying or dividing by 2
(u << 5) - (u << 3) == u * 24- because 32 - 8 = 2
- TODO: why does this not overflow sometimes????
- right shift is floored division (euclidean)
With signed integers, division gets more complicated → the MSB is also shifted somewhere → we need arithmetic shift commands!
- arithmetic shifting fills the left with 1s, as seen before this compensates
- but! it rounds the wrong direction with
u < 0! - we want to round towards 0!!!
- so we want but do
- compiler does this automatically by checking for negatives
2.1. Bit-Level Representation (CS:APP 2.1)
2.1.1. Byte Ordering
Endianness
A multi-byte object is stored at consecutive addresses, and its address is that of the lowest byte. Little-endian (x86) stores the least significant byte first; big-endian stores the most significant byte first.
Example: int x = 0x01234567 at address 0x100:
- little-endian:
0x100: 67 45 23 01 - big-endian:
0x100: 01 23 45 67
To detect the byte order we cast an int to (char *). Then we can access individual bytes.
2.1.2. Bitwise and Logical Operators
Bitwise vs Logical Operators
& | ^ ~act on each bit position independently.&& || !treat any nonzero value as true, return 0 or 1, and short-circuit.
Example (8-bit):
0x69 & 0x55→0x410x69 && 0x55→1~0x41→0xBE!0x41→0
Bit of x:
- set:
x | (1u << k) - clear:
x & ~(1u << k) - toggle:
x ^ (1u << k) - test:
(x >> k) & 1
2.1.3. Shifts
Shift Operations
We can shift either right or left:
- left shift:
x << kdrops the top bits and fills with 0 from the right.- right shift:
- A logical right shift fills with 0
- An arithmetic right shift fills with copies of the MSB
Note: an arithmetic shift occurs in C for signed types, not unsigned. Otherwise a logical shift is used.
Example: For x = 10100010:
x << 3→00010000- logical
x >> 2→00101000 - arithmetic
x >> 2→11101000
2.2. Integer Encodings (CS:APP 2.2)
2.2.1. Unsigned and Two’s Complement
Encodings
For a bit vector :
The MSB of a two’s-complement number has negative weight (e.g. for ).
Ranges and Identities
Note: Identities
-
- → asymmetric range (-128 to 127 for 8 bits)
- .
- The pattern of all ones is unsigned and signed.
Example

2.2.2. Signed–Unsigned Conversion
T2U and U2T
Casting between signed and unsigned values of the same width keeps the bits, and
Proof: For the same bit vector, , and exactly when the signed value is negative.
- the shift by
+TMAXonly happens when the sign bit is1!
Example:

Implicit Conversion:

Example: for (unsigned i = n-1; i >= 0; i--) never terminates because unsigned >= 0 is always true!
TODO
2.2.3. Sign Extension and Truncation
Sign Extension
Extending a -bit two’s-complement number to bits by prepending copies of the MSB preserves its value. Unsigned numbers are always zero-extended.
Proof: By induction on , so it suffices to show . If , nothing changes. If , the old MSB changes weight from to and the new MSB contributes . The net change in those two positions is , which equals the old contribution.
Example: 1011 becomes 11111011 , while unsigned 1011 becomes 00001011. For short sx = -12345;, the cast (unsigned) sx first sign-extends to int and then reinterprets: .
Truncation
Truncating to its low bits gives
Example: (short) 53191 .
2.3. Integer Arithmetic (CS:APP 2.3)
2.3.1. Addition
Unsigned Addition
. The carry out of the MSB is discarded in C; in assembly it survives in the carry flag
CF.
Group Structure
is an abelian group with identity 0 and inverse for (the inverse of 0 is 0).
Proof: It is with canonical representatives, so closure, associativity and commutativity are inherited.
Two's-Complement Addition
, i.e. the same adder circuit. Hence
(int)((unsigned)u + (unsigned)v) == u + v. For :
Overflow Detection
- Signed: overflow occurs iff and have the same sign and the result has the other sign. Equivalently, the carry into the MSB differs from the carry out of it.
- Unsigned: overflow occurs iff .
Example ():
| Sum | Bits | Result | Case |
|---|---|---|---|
0101 + 0101 = 1010 | positive overflow | ||
1000 + 1011 = (1)0011 | negative overflow | ||
| unsigned |
Signed overflow is UB in ISO C. The course models it as wraparound (as with gcc -fwrapv), but optimizers may assume it never happens, e.g. by folding x + 1 > x to true.
2.3.2. Negation
Two's-Complement Negation
for all , including .
Proof: is the all-ones pattern, i.e. ; add 1 to both sides. For : 100..0 011..1 100..0.
Example: 0101 1010 1011 . This explains 1 + (a << 3) + ~a .
2.3.3. Multiplication
Truncated Multiplication
The exact product of two -bit numbers needs up to bits; C keeps only the low :
and form the commutative ring .
Bit-Level Equivalence
and produce identical bit patterns: . One
imulinstruction therefore serves both.
Proof: , so . Both results are the same residue, reinterpreted.
Example (, 101 011):
| Interpretation | Exact product | Low 3 bits |
|---|---|---|
| unsigned | 001111 | 111 |
| signed | 110111 | 111 |
Mixed signs come out right (MSB set) as long as the true product fits in bits.
2.3.4. Multiplication by Constants
Shift as Multiplication
For signed and unsigned :
x << k. Hence, if has a run of ones from bit down to bit :A shift and an add take about 1 cycle each;
imultakes about 3.
Intermediate Overflow Cancels
(x << 5) - (x << 3) == x * 24holds for all , even whenx << 5overflows.
Proof: Every operation is in , so . x * 24 is reduced modulo as well, so both sides are the same residue, wraparound included.
→ It gives the correct answer, as long as the answer is representable!
- note this works since two’s complement works modulo for left shifts!
Example:
| Product | Shift form |
|---|---|
11000 | (x << 5) - (x << 3) |
(x << 3) - x | |
(x << 4) - (x << 1) | |
| (HS22 option d) | (a << 4) + (a << 2) + (a << 1) |
2.3.5. Division by Powers of Two
Unsigned Division by Shift
For unsigned :
u >> k, using a logical shift.
Proof: . The shift keeps and drops , which is exactly the remainder.
Signed Division by Shift
For signed , the arithmetic shift gives
x >> k, which rounds toward . C requires rounding toward 0, i.e. for . For :
Ceiling via Floor
For integers and : .
Proof: Write with . If , both sides equal . Otherwise , so the right side is .
Example: . Then -12340 >> 4 , which is wrong, while (-12340 + 15) >> 4 , which is correct. The compiler emits the sign test itself: ((a < 0) ? a + 3 : a) >> 2 == a / 4.
For other constants, compilers multiply by a reciprocal and shift. For example, unsigned 32-bit x / 3 becomes ((uint64_t)x * 0xAAAAAAAB) >> 33.