SNES 16.16 Fixed-Point Math. More...
Go to the source code of this file.
Macros | |
| #define | FIX32(x) |
| Convert integer to fixed32 (shift left 16). | |
| #define | FIX32_FRAC(x) |
| Get fractional part of a fixed32 as a u16 (0..65535). | |
| #define | FIX32_MAKE(i, f) |
| Build a fixed32 from integer + fractional parts. | |
| #define | UNFIX32(x) |
| Convert fixed32 to integer (floor). | |
Typedefs | |
| typedef s32 | fixed32 |
| 16.16 signed fixed-point type | |
Functions | |
| fixed32 | fix32Abs (fixed32 x) |
| Absolute value of a fixed32. | |
| fixed32 | fix32Clamp (fixed32 x, fixed32 min, fixed32 max) |
| Clamp x to [min, max]. | |
| fixed32 | fix32Cos (u8 angle) |
| fixed32 | fix32Div (fixed32 a, fixed32 b) |
| 16.16 fixed-point divide | |
| fixed32 | fix32Lerp (fixed32 a, fixed32 b, fixed32 t) |
| 16.16 fixed-point linear interpolation | |
| fixed32 | fix32Max (fixed32 a, fixed32 b) |
| Maximum of two fixed32. | |
| fixed32 | fix32Min (fixed32 a, fixed32 b) |
| Minimum of two fixed32. | |
| fixed32 | fix32Mul (fixed32 a, fixed32 b) |
| 16.16 fixed-point multiply | |
| fixed32 | fix32Sin (u8 angle) |
| 16.16 fixed-point sine of an 8-bit angle (0..255 = 0..360°) | |
SNES 16.16 Fixed-Point Math.
32-bit signed fixed-point arithmetic for world-space coordinates, physics velocity accumulators, and any quantity that needs more range or precision than the lib's 8.8 fixed type from <snes/math.h>.
The lib's existing 8.8 fixed type (range ±128, precision 1/256) is fine for screen-space coords and velocities. Use fixed32 when:
| Operation | Helper | Cost (cycles, approx) |
|---|---|---|
| Add/sub | + / - on s32 | inline, 8-10 |
| Negate | unary - | inline |
| Abs | fix32Abs | inline |
| Clamp | fix32Clamp | inline |
| Multiply | fix32Mul | ~280 (16 × 8x8 hw) |
Division (fix32Div, 0 on a zero divisor), sine / cosine (fix32Sin, fix32Cos) and fix32Lerp are declared below. (This line called them "deferred" long after they shipped.)
fixed32 is signed two's complement s32. Negative values are stored with the sign bit at position 31. Use FIX32(-5) for negative integers; the bit pattern handles itself. fix32Mul handles signs internally (XOR-then-negate-result).
| #define FIX32 | ( | x | ) |
| #define FIX32_FRAC | ( | x | ) |
| #define FIX32_MAKE | ( | i, | |
| f ) |
| #define UNFIX32 | ( | x | ) |
Convert fixed32 to integer (floor).
| x | fixed32 value |
Rounds toward MINUS infinity, not toward zero: the cc65816 backend implements the signed right shift as arithmetic, so UNFIX32 of -0.5 is -1. (This block claimed "toward zero for both signs" until 2026-09-20.)
16.16 signed fixed-point type
Range: -32768.0 to +32767.99998 Precision: 1/65536 ≈ 0.0000153
Absolute value of a fixed32.
| x | fixed32 |
The "undefined" edge is for x = -2^31 exactly, which has no positive representation in s32. The function returns INT32_MIN unchanged in that case, matching standard library abs behavior on overflow.
Clamp x to [min, max].
Caller's responsibility to ensure min <= max.
16.16 fixed-point divide
Algorithm: 48-iteration bit-by-bit long divide of (|a| << 16) by |b|, with sign-magnitude handling. The 48-bit dividend doesn't fit in a single 32-bit register, so we can't reuse tcc_udivmod32 directly — the custom loop processes one quotient bit per iteration with an 80-bit working register.
Cycles: ~1500 (much slower than fix32Mul; use sparingly in hot loops).
A zero divisor returns 0, like fixDiv() / div16() / mod16().
16.16 fixed-point linear interpolation
| a | Start value (returned when t = 0) |
| b | End value (returned when t = FIX32(1)) |
| t | Interpolation parameter in 16.16, typically [0, FIX32(1)] |
Inline: just a + fix32Mul(b - a, t). No new asm — the multiply carries the cost (~280 cycles), the add is one Kl op.
Extrapolation is supported: t > FIX32(1) extends past b, t < 0 extends before a. The caller is responsible for clamping if a strict interpolation is needed.
Range caveat: b - a is a 32-bit difference. When a and b have opposite signs and |b - a| reaches 32768.0 (say a = FIX32(-20000), b = FIX32(20000)) it wraps and the result is wrong, not imprecise — nothing in the multiply can recover it. For such spans use fix32Mul(a, FIX32(1) - t) + fix32Mul(b, t) (one extra mul, no difference). (Until 2026-10-04 this note spoke of lost precision.)
16.16 fixed-point multiply
Algorithm: three 16×16→32 unsigned partial products (a_l*b_l, a_l*b_h, a_h*b_l) + one 16×16→16 (low 16 of a_h*b_h) combined as result = ml1 + ml2 + (ll >> 16) + (hh_lo << 16) Each 16×16→32 uses 4 hardware 8×8 multiplies — ~280 cycles total.
Sign-magnitude internally: result = sign(a) XOR sign(b) applied to the unsigned 32-bit magnitude. Overflow wraps modulo 2^32.
16.16 fixed-point sine of an 8-bit angle (0..255 = 0..360°)
| angle | 0=0°, 64=90°, 128=180°, 192=270° |
Lifted from the existing 8.8 fixSin LUT by shifting left 8 bits to fill the upper half of the 16-bit fractional field. The lower 8 bits are always zero (no precision gained beyond what the 8.8 LUT provides). Cost: one LUT lookup, a sign-extend and a shift (the measured figure is in docs/PERF.md; "about 30 cycles" stood here until 2026-10-04).
Precision: each LUT step is 1/256 ≈ 0.0039, so for fine animation (sub-pixel motion over many frames) this is adequate. For high- precision physics that compound thousands of operations, the 8-bit-fractional limit may show as drift; a future chantier could add a 16-bit LUT for a 256× precision improvement at 512 bytes ROM.