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