OpenSNES
Modern Open-Source SNES Development SDK
Loading...
Searching...
No Matches
fixed32.h File Reference

SNES 16.16 Fixed-Point Math. More...

#include <snes/types.h>
#include <snes/math.h>

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°)

Detailed Description

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>.

Overview

  • 16-bit integer part: -32768 to +32767 (enough for a 32k-tile world)
  • 16-bit fractional part: 1/65536 ≈ 0.0000153 precision

The lib's existing 8.8 fixed type (range ±128, precision 1/256) is fine for screen-space coords and velocities. Use fixed32 when:

  • The world is larger than 128 tiles in any dimension.
  • Accumulator drift over many frames matters (e.g. sub-pixel motion that must integrate without bias over thousands of frames).
  • Two values multiplied risk overflowing 8.8 (e.g. velocity × time).
fixed32 omega = FIX32(1) / 360; // 1°/frame in radians (~0.0028)
angle += omega; // free — just s32 += s32
fixed32 fix32Mul(fixed32 a, fixed32 b)
16.16 fixed-point multiply
#define FIX32(x)
Convert integer to fixed32 (shift left 16).
Definition fixed32.h:82
s32 fixed32
16.16 signed fixed-point type
Definition fixed32.h:68
fixed32 fix32Sin(u8 angle)
16.16 fixed-point sine of an 8-bit angle (0..255 = 0..360°)
static u16 angle
Rotation step index, 0..47.
Definition main.c:53

Operations available

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).

Sign convention

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).

Author
OpenSNES Team

Macro Definition Documentation

◆ FIX32

#define FIX32 ( x)
Value:
((fixed32)((u32)(s32)(x) << 16))
signed int s32
32-bit signed integer (-2147483648 to 2147483647)
Definition types.h:79
unsigned int u32
32-bit unsigned integer (0 to 4294967295)
Definition types.h:90

Convert integer to fixed32 (shift left 16).

Parameters
xInteger value (-32768 to +32767)
Returns
fixed32 representation
fixed32 pos = FIX32(1000); // 1000.0
fixed32 half = FIX32(1) >> 1; // 0.5 (32768 in raw)

◆ FIX32_FRAC

#define FIX32_FRAC ( x)
Value:
((u16)((fixed32)(x) & 0xFFFF))
unsigned short u16
16-bit unsigned integer (0 to 65535)
Definition types.h:53

Get fractional part of a fixed32 as a u16 (0..65535).

Parameters
xfixed32 value
Returns
Low 16 bits (the fractional part)
fixed32 q = FIX32(1) >> 2; // 0.25
u16 frac = FIX32_FRAC(q); // 16384
#define FIX32_FRAC(x)
Get fractional part of a fixed32 as a u16 (0..65535).
Definition fixed32.h:111

◆ FIX32_MAKE

#define FIX32_MAKE ( i,
f )
Value:
(((fixed32)(s16)(i) << 16) | (fixed32)(u16)(f))
static u8 i
Definition main.c:156
signed short s16
16-bit signed integer (-32768 to 32767)
Definition types.h:50

Build a fixed32 from integer + fractional parts.

Parameters
iInteger part (s16)
fFractional part (u16)
Returns
Combined fixed32
fixed32 v = FIX32_MAKE(100, 32768); // 100.5
#define FIX32_MAKE(i, f)
Build a fixed32 from integer + fractional parts.
Definition fixed32.h:123

◆ UNFIX32

#define UNFIX32 ( x)
Value:
((s16)((fixed32)(x) >> 16))

Convert fixed32 to integer (truncate toward zero).

Parameters
xfixed32 value
Returns
Integer part as s16

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).

fixed32 pos = FIX32(1000) + 32768; // 1000.5
s16 screen = UNFIX32(pos); // 1000
#define UNFIX32(x)
Convert fixed32 to integer (truncate toward zero).
Definition fixed32.h:99

Typedef Documentation

◆ fixed32

typedef s32 fixed32

16.16 signed fixed-point type

Range: -32768.0 to +32767.99998 Precision: 1/65536 ≈ 0.0000153

Function Documentation

◆ fix32Abs()

fixed32 fix32Abs ( fixed32 x)
inline

Absolute value of a fixed32.

Parameters
xfixed32
Returns
|x| (or INT32_MIN for INT32_MIN — the one-value undefined edge)

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.

◆ fix32Clamp()

fixed32 fix32Clamp ( fixed32 x,
fixed32 min,
fixed32 max )
inline

Clamp x to [min, max].

Returns
min if x < min, max if x > max, else x

Caller's responsibility to ensure min <= max.

◆ fix32Cos()

fixed32 fix32Cos ( u8 angle)

◆ fix32Div()

fixed32 fix32Div ( fixed32 a,
fixed32 b )

16.16 fixed-point divide

Parameters
aNumerator
bDenominator (must be non-zero; division by zero is undefined)
Returns
(a / b) at 16.16 precision (low 32 bits of (a << 16) / b)

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).

fixed32 velocity = fix32Div(distance, time);
fixed32 ratio = fix32Div(width, FIX32(2)); // halve width
fixed32 fix32Div(fixed32 a, fixed32 b)
16.16 fixed-point divide

◆ fix32Lerp()

fixed32 fix32Lerp ( fixed32 a,
fixed32 b,
fixed32 t )
inline

16.16 fixed-point linear interpolation

Parameters
aStart value (returned when t = 0)
bEnd value (returned when t = FIX32(1))
tInterpolation parameter in 16.16, typically [0, FIX32(1)]
Returns
a + (b - a) * t at 16.16 precision

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).

fixed32 midpoint = fix32Lerp(p0, p1, FIX32(1) >> 1); // (p0 + p1) / 2
fixed32 eased = fix32Lerp(start, end, easing_curve_t);
u8 p1[]
Player 1 state, initialized at left-center of screen.
Definition main.c:101
u8 p0[]
fixed32 fix32Lerp(fixed32 a, fixed32 b, fixed32 t)
16.16 fixed-point linear interpolation
Definition fixed32.h:239

◆ fix32Max()

fixed32 fix32Max ( fixed32 a,
fixed32 b )
inline

Maximum of two fixed32.

◆ fix32Min()

fixed32 fix32Min ( fixed32 a,
fixed32 b )
inline

Minimum of two fixed32.

◆ fix32Mul()

fixed32 fix32Mul ( fixed32 a,
fixed32 b )

16.16 fixed-point multiply

Parameters
aFirst operand
bSecond operand
Returns
(a * b) at 16.16 precision (bits 16-47 of the full 64-bit product)

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.

fixed32 area = fix32Mul(FIX32(width), FIX32(height));
fixed32 dy = fix32Mul(velocity, FIX32(dt));

◆ fix32Sin()

fixed32 fix32Sin ( u8 angle)

16.16 fixed-point sine of an 8-bit angle (0..255 = 0..360°)

Parameters
angle0=0°, 64=90°, 128=180°, 192=270°
Returns
sin(angle) in 16.16, range [FIX32(-1), FIX32(1)]

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.