Skip to content

Fixed-Point Math

Fractional values without floating point. Essential for smooth movement and physics on 8/16-bit CPUs.

mathphysicsmovementfixed-point

Overview

8-bit and 16-bit CPUs don’t have floating-point hardware. Fixed-point math lets you work with fractional values using integers. The trick: store position as two bytes — the high byte is the pixel position, the low byte is the sub-pixel fraction.

This gives you smooth movement at any speed, even slower than 1 pixel per frame.

Concept

; 8.8 fixed-point: 8 bits integer, 8 bits fraction
; Value $0180 = 1.5 (1 pixel + 128/256 = 1.5)
; Value $0040 = 0.25 (0 pixels + 64/256 = 0.25)

position_lo: byte    ; Fractional part (0-255 = 0.0 to 0.996)
position_hi: byte    ; Integer part (pixel position)

velocity_lo: byte    ; Fractional velocity
velocity_hi: byte    ; Integer velocity (usually 0 for slow movement)

Pseudocode

; Player position (8.8 fixed-point)
player_x_lo: byte = 0      ; Fraction
player_x_hi: byte = 100    ; Pixel position

; Velocity: 0.75 pixels per frame
; 0.75 * 256 = 192 = $C0
velocity_x_lo: byte = $C0
velocity_x_hi: byte = 0

; Update position each frame
update_position:
    ; Add velocity to position (16-bit add)
    player_x_lo = player_x_lo + velocity_x_lo
    if carry:
        player_x_hi = player_x_hi + 1
    player_x_hi = player_x_hi + velocity_x_hi

    ; Use player_x_hi as sprite X position
    sprite_x = player_x_hi
    return

Implementation Notes

6502:

; Position
player_x_lo:  .byte 0
player_x_hi:  .byte 100

; Velocity (0.75 px/frame = $00C0)
velocity_x_lo: .byte $C0
velocity_x_hi: .byte $00

update_position:
    clc
    lda player_x_lo
    adc velocity_x_lo
    sta player_x_lo
    lda player_x_hi
    adc velocity_x_hi
    sta player_x_hi
    rts

Z80:

; Add 16-bit velocity to 16-bit position
update_position:
    ld hl,(player_x)       ; L=lo, H=hi
    ld bc,(velocity_x)     ; C=lo, B=hi
    add hl,bc
    ld (player_x),hl
    ret

Common Velocities

Speed (px/frame)Fixed-point valueHex
0.2564$0040
0.5128$0080
0.75192$00C0
1.0256$0100
1.5384$0180
2.0512$0200
2.5640$0280

Trade-offs

AspectCost
CPU~20 cycles for 16-bit add
Memory2 bytes per axis (vs 1 for integer)
Precision1/256 pixel (~0.004 pixel resolution)

When to use: Any movement that isn’t exactly integer pixels per frame — gravity, acceleration, diagonal movement, slow enemies.

When to avoid: Grid-based games where everything moves in whole tiles.

Gravity Example

; Gravity: accelerate downward 0.1 px/frame/frame
GRAVITY = 26      ; 0.1 * 256 ≈ 26

velocity_y_lo: byte = 0
velocity_y_hi: byte = 0

apply_gravity:
    ; Add gravity to velocity
    velocity_y_lo = velocity_y_lo + GRAVITY
    if carry:
        velocity_y_hi = velocity_y_hi + 1

    ; Clamp to terminal velocity (e.g., 4 px/frame)
    if velocity_y_hi >= 4:
        velocity_y_hi = 4
        velocity_y_lo = 0
    return

Signed Movement

For movement in both directions, use signed arithmetic or separate positive/negative velocities. Simplest approach: track direction separately and always add positive velocity.