The Need for Fixed-Point Math
As established (Part 30), Z80 floating-point math is extremely slow. Fixed-point arithmetic is the solution: it allows the Z80 to perform calculations involving fractions using only fast integer operations.
The Principle: A fixed-point number is an integer where the position of the decimal point is implied and fixed by the programmer.
The Q-Format (Standard Fixed-Point)
We represent a fractional number (like 3.14) by scaling it up to make it an integer. This is often called a Q-format (e.g., Q16, Q8).
Example: Q8.8 Format (16-bit Integer) A 16-bit word is split in half:
- High 8 bits (Q8): The Integer part (values ≥ 1).
- Low 8 bits (.8): The Fractional part (values < 1).
Scaling Factor: Since $2^8 = 256$, the low 8 bits represent $1/256^{ths}$. The number $1.0$ is stored as $256$ decimal. The number $0.5$ is stored as $128$.
Fixed-Point Addition and Subtraction
Addition and subtraction are simple and fast because you treat the fixed-point numbers as ordinary 16-bit integers. The implicit decimal point takes care of itself.
Example (Q8.8): If $A = 1.5$ (stored as 384) and $B = 0.5$ (stored as 128):
ADD HL, DE
(384 + 128 = 512).- $512$ divided by the scaling factor (256) is $2.0$. Correct!
Fixed-Point Multiplication
Multiplication requires an extra step because the result is doubly scaled.
The Problem: Multiplying two Q8.8 numbers yields a Q8.16 number (32 bits), which is too large. You must divide the 32-bit result by the scaling factor ($256$) to restore the correct format.
The Solution: After performing the 16x16 multiplication (Part 16), you only need to extract the middle 16 bits of the 32-bit result. This is a fast operation in Z80:
; Assume 32-bit result is stored in DEHL (DE = High, HL = Low)
; The desired Q8.8 result is the high byte of HL and the low byte of DE.
; Simplest method: Discard L and D, and keep E and H.
LD L, E ; L ← Low 8 bits of the integer part
LD H, D ; H ← High 8 bits of the integer part
; Final Q8.8 result is now in HL.
Fixed-Point Division
Division is complex. The fastest method is to multiply the Dividend by the scaling factor first, and then perform a standard integer division. This restores the correct Q-format before the final division.