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 (truncate toward zero). | |
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, sin/cos, and lerp are deferred to follow-up chantiers (see .claude/notes/chantiers/b5_fix32_orbit_sketch.md).
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 (truncate toward zero).
| x | fixed32 value |
Truncation rounds toward zero for both signs (C99 semantics for arithmetic right-shift on signed types is implementation-defined, but the cc65816 backend implements arithmetic shift, so this works as expected on this target).
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).
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.
Precision caveat: when (b - a) approaches the fix32 range limit (close to ±32768), the intermediate multiply may lose precision at the bottom of the fractional part. For tight cases, prefer direct a*(1-t) + b*t formulation (one extra mul, no subtraction round-trip).
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). Costs: one LUT lookup + sign-extend + shift — about 30 cycles total.
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.