Chapter 2 Exercises: Numbers in the Machine
Section A: Binary and Hexadecimal Conversion
Exercise 2.1 — Number Base Conversions
Convert each of the following values. Show your work.
a) 0b10110111 from binary to hexadecimal and to decimal (unsigned)
b) 0xFF3C from hexadecimal to binary and to decimal (unsigned)
c) 347 from decimal to hexadecimal and to binary
d) 0x80000000 from hexadecimal to decimal (unsigned 32-bit) and to decimal (signed 32-bit, two's complement)
e) -1 as a signed 64-bit two's complement value in hexadecimal
Exercise 2.2 — Two's Complement
For each of the following, give the 8-bit two's complement representation, or state that the value cannot be represented:
a) +127
b) -128
c) -1
d) -64
e) +128 (cannot fit — explain why and what happens if you store it anyway)
f) -129 (cannot fit — explain why)
Exercise 2.3 — The Flip-and-Add-1 Proof
Using the flip-and-add-1 method, find the 16-bit two's complement negation of each value. Verify by adding the original and its negation (the result should be 0 in 16-bit arithmetic, with a carry out):
a) 0x0005 (+5)
b) 0x0100 (+256)
c) 0x8000 (-32768 — this one is special, explain why)
d) 0x7FFF (+32767)
Section B: Flag Prediction
Exercise 2.4 — Predict the Flags
For each instruction sequence, predict the final value of CF, OF, SF, and ZF. Assume all registers start at 0 and are 32-bit (use EAX, EBX, etc.):
a)
mov eax, 0xFF
add eax, 1
b)
mov eax, 0x7FFFFFFF
add eax, 1
c)
mov eax, 0xFFFFFFFF
add eax, 1
d)
mov eax, 100
sub eax, 200
(Hint: think about whether this is signed or unsigned and which flag each represents)
e)
mov eax, 5
neg eax
add eax, 5
f)
xor eax, eax
(What are ZF and the other flags after this?)
Exercise 2.5 — Register Trace
Fill in the complete register trace table. All operations use 64-bit registers (RAX, RBX, RCX):
| Instruction | RAX | RBX | RCX | CF | OF | SF | ZF |
|---|---|---|---|---|---|---|---|
| (initial) | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
mov rax, 0x7FFFFFFFFFFFFFFF |
|||||||
mov rbx, 1 |
|||||||
add rax, rbx |
|||||||
mov rcx, rax |
|||||||
neg rcx |
|||||||
add rax, rcx |
Exercise 2.6 — Signed vs. Unsigned Comparisons
Given the following code, determine whether each conditional jump is taken:
mov eax, 0xFF ; what is this as unsigned? as signed?
mov ebx, 0x01
cmp eax, ebx ; sets flags based on eax - ebx
; For each of the following, is the jump taken?
; (a) ja label ; jump if above (unsigned >)
; (b) jg label ; jump if greater (signed >)
; (c) jb label ; jump if below (unsigned <)
; (d) jl label ; jump if less (signed <)
; (e) jz label ; jump if zero (equal)
Explain each answer in terms of what cmp eax, ebx does to the flags when eax = 0xFF (255 unsigned, -1 signed) and ebx = 1.
Section C: Sign Extension
Exercise 2.7 — MOVSX vs MOVZX
What value does RAX contain after each of the following? Explain why:
; (a)
mov al, 0x80
movzx rax, al
; (b)
mov al, 0x80
movsx rax, al
; (c)
mov al, 0x7F
movzx rax, al
; (d)
mov al, 0x7F
movsx rax, al
; (e) -- a common bug:
mov rax, 0xDEADBEEFCAFEBABE
mov al, 0x80
; What is rax now? (do NOT use movsx/movzx -- just the mov al)
Exercise 2.8 — Sign Extension in Practice
The following C function:
int array_access(int *arr, signed char index) {
return arr[index];
}
Compiles to code that must sign-extend index (a signed 8-bit value) to a 64-bit value for use in the memory address calculation. Write the NASM assembly for the body of this function following the System V AMD64 ABI (arr in RDI, index in SIL — the byte register for RSI):
array_access:
; rdi = arr (pointer)
; sil = index (signed byte)
; Your code here:
; 1. Sign-extend sil to rsi (hint: use movsx)
; 2. Compute the address: arr + index*4 (ints are 4 bytes)
; 3. Load the value: mov eax, [address]
; 4. ret
What would happen if you accidentally used movzx instead of movsx and index was -1?
Section D: Floating-Point Representation
Exercise 2.9 — IEEE 754 Encoding
For each of the following decimal values, give the IEEE 754 single-precision (32-bit) bit pattern in binary and hexadecimal:
a) 1.0
b) -1.0
c) 2.0
d) 0.5
e) 0.25
f) 0.1 (will be approximate — show the closest representable value)
For (f), explain why no finite binary fraction exactly represents 0.1.
Exercise 2.10 — Floating-Point Pitfalls
Without running any code, predict whether each comparison will be true or false, and explain why:
float a = 0.1f;
float b = 0.2f;
float c = 0.3f;
(a) a + b == c // true or false?
(b) a + b == 0.3f // true or false? same as (a)?
(c) (1.0f/3.0f) * 3.0f == 1.0f // true or false?
(d) 1e38f + 1e38f > 0 // true or false?
(e) 1e38f * 2 > 0 // true or false?
For (d) and (e): what is the difference in what overflows?
Exercise 2.11 — NaN Behavior
The IEEE 754 specification states that NaN does not equal any value, including itself. Given this, explain the output of the following C code:
float x = 0.0f / 0.0f; // produces NaN
printf("%d\n", x == x); // prints 0 or 1?
printf("%d\n", x != x); // prints 0 or 1?
printf("%d\n", x < x); // prints 0 or 1?
printf("%d\n", x > x); // prints 0 or 1?
How would you check for NaN in x86-64 assembly using SSE instructions? (The UCOMISS instruction sets ZF=1, PF=1, CF=1 when comparing unordered operands, i.e., when either operand is NaN.)
Section E: Endianness
Exercise 2.12 — Little-Endian Memory Layout
Given the following NASM data declaration:
section .data
values: dq 0x0102030405060708
dw 0x0910
db 0x0B
If values starts at address 0x400000, what byte value is stored at each of the following addresses?
a) 0x400000
b) 0x400001
c) 0x400007
d) 0x400008
e) 0x40000A
Exercise 2.13 — BSWAP and Network Byte Order
A network packet contains a 4-byte big-endian integer at a known address. The bytes in memory (in order from low to high address) are: 0x00, 0x00, 0x1A, 0x85.
a) What is the decimal value of this 32-bit big-endian integer?
b) If you load it with mov eax, [packet_field] on a little-endian x86-64 machine, what value does EAX contain?
c) After bswap eax, what value does EAX contain?
d) Verify that this is correct by converting the big-endian bytes manually.
Section F: Synthesis
Exercise 2.14 — Full Trace with Flags
Trace through the following complete program (on paper) and determine the final value printed. Show every register state change:
section .text
global _start
_start:
mov rax, 100
mov rbx, 200
add rax, rbx ; Step 1: what are rax and flags now?
mov rcx, rax
neg rcx ; Step 2: what is rcx?
add rax, rcx ; Step 3: what is rax? what are flags?
jnz .not_zero ; Step 4: is this jump taken?
; If we reach here:
mov rax, 60
mov rdi, 42 ; exit code 42
syscall
.not_zero:
mov rax, 60
mov rdi, 0 ; exit code 0
syscall
What exit code does the program produce? Explain step by step.
Exercise 2.15 — Overflow Detection Pattern
Write a function in NASM assembly that performs safe (overflow-checked) addition of two 64-bit signed integers. The function should: - Take arguments in RDI and RSI - Return the sum in RAX if no overflow occurred - Return INT64_MAX (0x7FFFFFFFFFFFFFFF) if positive overflow occurred - Return INT64_MIN (0x8000000000000000) if negative overflow occurred
; safe_add: overflow-checked 64-bit signed addition
; Arguments: rdi = a, rsi = b
; Returns: rax = result (or clamped value on overflow)
safe_add:
; Your implementation here.
; Hints:
; - ADD sets OF on signed overflow
; - Use JO to branch on overflow
; - To determine which direction the overflow went, check the sign of the operands
ret
Test cases to verify:
- safe_add(100, 200) → 300
- safe_add(INT64_MAX, 1) → INT64_MAX (clamped)
- safe_add(INT64_MIN, -1) → INT64_MIN (clamped)
- safe_add(-1, -1) → -2