Skip to main content

embedded_dsp/
fixed_point.rs

1//! Q16.16 fixed-point math.
2//!
3//! A saturating fixed-point number format (`i32`, 16 fractional bits) for
4//! pipelines that can't afford float math (no FPU, or the accuracy/latency
5//! trade-off isn't worth it): rasterizer transforms, angle math, scanline
6//! interpolation. Complements the `q7`/`q15`/`q31`/`q63` aliases in
7//! [`crate::types`], which are narrower fixed-point formats aimed at
8//! CMSIS-DSP-style signal processing rather than screen-space geometry.
9//!
10//! - Type alias [`Q16`], plus [`FP_ONE`], [`Q16_MAX`], [`Q16_MIN`]
11//! - Conversion: `f32 <-> Q16`, `i16 <-> Q16`, `q31 <-> Q16`
12//! - Arithmetic: [`mul_q16`], [`mul_n_q16`], [`mul_f_q16`], [`div_q16`], [`div_n_q16`], [`div_f_q16`]
13//! - Saturating: [`qadd_q16`], [`qsub_q16`], [`abs_q16`]
14//! - Helpers: [`lerp_q16`], [`angle_to_q16`], [`recip_q16`]
15//! - Trig (with the `lut` feature): [`crate::lut::sin_q16`], [`crate::lut::cos_q16`]
16//! - [`ScanlineInterp`] — accelerated per-scanline z + (u, v) interpolation
17
18// ─────────────────────────────────────────────────────────────────────────────
19// Constants
20// ─────────────────────────────────────────────────────────────────────────────
21
22/// Fractional bit count for Q16.16.
23pub const FP_SHIFT: u32 = 16;
24
25/// 1.0 in Q16.16 representation.
26pub const FP_ONE: i32 = 1_i32 << FP_SHIFT;
27
28/// Maximum positive value of a Q16.16 number (same as i32::MAX).
29pub const Q16_MAX: i32 = i32::MAX;
30
31/// Minimum value of a Q16.16 number (same as i32::MIN).
32pub const Q16_MIN: i32 = i32::MIN;
33
34/// Type alias: `Q16` is `i32` stored in Q16.16 fixed-point format.
35///
36/// The integer part occupies bits 31..16, the fractional part bits 15..0.
37pub type Q16 = i32;
38
39// ─────────────────────────────────────────────────────────────────────────────
40// Conversions
41// ─────────────────────────────────────────────────────────────────────────────
42
43/// Convert `f32` -> `Q16.16` with correct rounding.
44#[inline(always)]
45pub fn to_q16(v: f32) -> Q16 {
46    (v * 65536.0_f32 + if v >= 0.0 { 0.5 } else { -0.5 }) as i32
47}
48
49/// Convert `Q16.16` -> `f32`.
50#[inline(always)]
51pub fn from_q16(v: Q16) -> f32 {
52    v as f32 / 65536.0_f32
53}
54
55/// Convert `i16` integer -> `Q16.16` (shift left 16).
56#[inline(always)]
57pub fn from_i16_q16(v: i16) -> Q16 {
58    (v as i32) << 16
59}
60
61/// Convert `Q16.16` -> `i16` integer (truncate fractional bits).
62#[inline(always)]
63pub fn to_i16_q16(v: Q16) -> i16 {
64    (v >> 16) as i16
65}
66
67/// Reinterpret a Q31 value as Q16.16 by shifting right 15 bits.
68#[inline(always)]
69pub fn q31_to_q16(v: i32) -> Q16 {
70    v >> 15
71}
72
73/// Reinterpret Q16.16 as Q31 by shifting left 16 bits (use i64 to avoid overflow).
74#[inline(always)]
75pub fn q16_to_q31(v: Q16) -> i64 {
76    (v as i64) << 16
77}
78
79// ─────────────────────────────────────────────────────────────────────────────
80// Arithmetic
81// ─────────────────────────────────────────────────────────────────────────────
82
83/// Multiply two Q16.16 values. Uses i64 intermediate (SMULL on Cortex-M).
84#[inline(always)]
85pub fn mul_q16(a: Q16, b: Q16) -> Q16 {
86    ((a as i64 * b as i64) >> 16) as i32
87}
88
89/// Multiply a Q16.16 value by a plain `i32` integer (no fractional scaling).
90#[inline(always)]
91pub fn mul_n_q16(a: Q16, n: i32) -> Q16 {
92    a.wrapping_mul(n)
93}
94
95/// Multiply a Q16.16 value by an `f32` scalar.
96#[inline(always)]
97pub fn mul_f_q16(a: Q16, f: f32) -> Q16 {
98    mul_q16(a, to_q16(f))
99}
100
101/// Divide two Q16.16 values. Returns 0 on division by zero.
102#[inline(always)]
103pub fn div_q16(a: Q16, b: Q16) -> Q16 {
104    if b == 0 {
105        return 0;
106    }
107    (((a as i64) << 16) / b as i64) as i32
108}
109
110/// Divide a Q16.16 value by a plain `i32` integer. Returns 0 on division by zero.
111#[inline(always)]
112pub fn div_n_q16(a: Q16, n: i32) -> Q16 {
113    if n == 0 {
114        return 0;
115    }
116    a / n
117}
118
119/// Divide a Q16.16 value by an `f32` scalar.
120#[inline(always)]
121pub fn div_f_q16(a: Q16, f: f32) -> Q16 {
122    div_q16(a, to_q16(f))
123}
124
125// ─────────────────────────────────────────────────────────────────────────────
126// Saturating arithmetic
127// ─────────────────────────────────────────────────────────────────────────────
128
129/// Saturating add: clamps the result to `[i32::MIN, i32::MAX]`.
130#[inline(always)]
131pub fn qadd_q16(a: Q16, b: Q16) -> Q16 {
132    (a as i64 + b as i64).clamp(Q16_MIN as i64, Q16_MAX as i64) as i32
133}
134
135/// Saturating subtract: clamps the result to `[i32::MIN, i32::MAX]`.
136#[inline(always)]
137pub fn qsub_q16(a: Q16, b: Q16) -> Q16 {
138    (a as i64 - b as i64).clamp(Q16_MIN as i64, Q16_MAX as i64) as i32
139}
140
141/// Absolute value of a Q16.16 number.
142#[inline(always)]
143pub fn abs_q16(a: Q16) -> Q16 {
144    a.abs()
145}
146
147// ─────────────────────────────────────────────────────────────────────────────
148// Helpers
149// ─────────────────────────────────────────────────────────────────────────────
150
151/// Linear interpolation in Q16.16:  `a + (b - a) * t / denom`.
152///
153/// `t` and `denom` are plain integers (scan-line step counts).
154/// Uses i64 to avoid overflow on large `(b - a)` spans.
155#[inline(always)]
156pub fn lerp_q16(a: Q16, b: Q16, t: i32, denom: i32) -> Q16 {
157    if denom == 0 {
158        return a;
159    }
160    let diff = b as i64 - a as i64;
161    (a as i64 + diff * t as i64 / denom as i64) as i32
162}
163
164/// Convert an angle in degrees to Q16.16 radians.
165#[inline(always)]
166pub fn angle_to_q16(degrees: f32) -> Q16 {
167    to_q16(degrees * core::f32::consts::PI / 180.0)
168}
169
170/// Fast reciprocal approximation: `1.0 / v` in Q16.16.
171///
172/// Uses integer shift: `(1 << 32) / v` — gives approx 6 correct decimal digits.
173/// Returns `Q16_MAX` for zero or near-zero input (safe sentinel).
174#[inline(always)]
175pub fn recip_q16(v: Q16) -> Q16 {
176    if v == 0 {
177        return Q16_MAX;
178    }
179    ((1i64 << 32) / v as i64) as i32
180}
181
182// ─────────────────────────────────────────────────────────────────────────────
183// ScanlineInterp — accelerated z-buffer + UV interpolation
184// ─────────────────────────────────────────────────────────────────────────────
185
186/// Per-scanline interpolator for z-buffer depth and (u, v) texture coordinates.
187///
188/// Pre-computes Q16.16 per-pixel step values for `z`, `u`, and `v` so the
189/// inner loop only does three wrapping additions instead of floating-point
190/// divisions per pixel — useful for MCU scanline rasterization.
191///
192/// # Usage
193/// ```rust,no_run
194/// # use embedded_dsp::fixed_point::ScanlineInterp;
195/// # let left_z = 0u32;
196/// # let right_z = 0u32;
197/// # let left_u = 0u32;
198/// # let right_u = 0u32;
199/// # let left_v = 0u32;
200/// # let right_v = 0u32;
201/// # let span_pixels = 0i32;
202/// let mut interp = ScanlineInterp::new(
203///     left_z,  right_z,   // u32 depth values (Q16.16)
204///     left_u,  right_u,   // u32 U texture coords (Q16.16)
205///     left_v,  right_v,   // u32 V texture coords (Q16.16)
206///     span_pixels,         // number of pixels across the scanline
207/// );
208///
209/// for _x in 0..=span_pixels {
210///     let z = interp.z();
211///     let u = interp.u();
212///     let v = interp.v();
213///     // ... depth test, texture sample, write pixel ...
214///     interp.step();
215/// }
216/// ```
217#[derive(Debug, Clone, Copy)]
218pub struct ScanlineInterp {
219    z_cur: u32,
220    z_step: i32,
221    u_cur: u32,
222    u_step: i32,
223    v_cur: u32,
224    v_step: i32,
225}
226
227impl ScanlineInterp {
228    /// Create a new scanline interpolator.
229    ///
230    /// # Arguments
231    /// - `z_left`, `z_right` — depth at the left and right scanline endpoints (Q16.16 `u32`)
232    /// - `u_left`, `u_right` — U texture coordinates (Q16.16 `u32`, range `[0, 65536]`)
233    /// - `v_left`, `v_right` — V texture coordinates (Q16.16 `u32`, range `[0, 65536]`)
234    /// - `span` — number of pixels across the scanline (0 is valid — returns left values)
235    #[inline]
236    pub fn new(
237        z_left: u32,
238        z_right: u32,
239        u_left: u32,
240        u_right: u32,
241        v_left: u32,
242        v_right: u32,
243        span: i32,
244    ) -> Self {
245        let (z_step, u_step, v_step) = if span > 0 {
246            let z_step = ((z_right as i64 - z_left as i64) / span as i64) as i32;
247            let u_step = ((u_right as i64 - u_left as i64) / span as i64) as i32;
248            let v_step = ((v_right as i64 - v_left as i64) / span as i64) as i32;
249            (z_step, u_step, v_step)
250        } else {
251            (0, 0, 0)
252        };
253        Self {
254            z_cur: z_left,
255            z_step,
256            u_cur: u_left,
257            u_step,
258            v_cur: v_left,
259            v_step,
260        }
261    }
262
263    /// Create an interpolator for depth-only scanlines (no texture mapping).
264    #[inline]
265    pub fn depth_only(z_left: u32, z_right: u32, span: i32) -> Self {
266        Self::new(z_left, z_right, 0, 0, 0, 0, span)
267    }
268
269    /// Current depth value (Q16.16 `u32`).
270    #[inline(always)]
271    pub fn z(&self) -> u32 {
272        self.z_cur
273    }
274
275    /// Current U texture coordinate (Q16.16 `u32`).
276    #[inline(always)]
277    pub fn u(&self) -> u32 {
278        self.u_cur
279    }
280
281    /// Current V texture coordinate (Q16.16 `u32`).
282    #[inline(always)]
283    pub fn v(&self) -> u32 {
284        self.v_cur
285    }
286
287    /// Advance all interpolators by one pixel.
288    #[inline(always)]
289    pub fn step(&mut self) {
290        self.z_cur = self.z_cur.wrapping_add_signed(self.z_step);
291        self.u_cur = self.u_cur.wrapping_add_signed(self.u_step);
292        self.v_cur = self.v_cur.wrapping_add_signed(self.v_step);
293    }
294
295    /// Advance `n` pixels at once (useful for skipping clipped scanline segments).
296    #[inline]
297    pub fn step_n(&mut self, n: i32) {
298        self.z_cur = self.z_cur.wrapping_add_signed(self.z_step.wrapping_mul(n));
299        self.u_cur = self.u_cur.wrapping_add_signed(self.u_step.wrapping_mul(n));
300        self.v_cur = self.v_cur.wrapping_add_signed(self.v_step.wrapping_mul(n));
301    }
302
303    /// Current depth as `f32`.
304    #[inline(always)]
305    pub fn z_f32(&self) -> f32 {
306        self.z_cur as f32 / 65536.0
307    }
308}
309
310// ─────────────────────────────────────────────────────────────────────────────
311// Tests
312// ─────────────────────────────────────────────────────────────────────────────
313
314#[cfg(test)]
315mod tests {
316    extern crate std;
317    use super::*;
318
319    #[test]
320    fn roundtrip_f32_q16() {
321        let values = [0.0f32, 0.25, -0.5, 1.0, 3.14159, -100.0, 32767.0];
322        for v in values {
323            let q = to_q16(v);
324            let back = from_q16(q);
325            let lsb = 1.0 / 65536.0_f32;
326            assert!(
327                (back - v).abs() <= lsb,
328                "roundtrip failed for {v}: got {back}"
329            );
330        }
331    }
332
333    #[test]
334    fn roundtrip_i16_q16() {
335        for v in [-32768i16, -1, 0, 1, 100, 32767] {
336            let q = from_i16_q16(v);
337            let back = to_i16_q16(q);
338            assert_eq!(back, v, "i16 roundtrip failed for {v}");
339        }
340    }
341
342    #[test]
343    fn q31_q16_shifts() {
344        let q31_one = 0x7FFF_FFFFi32;
345        let q16 = q31_to_q16(q31_one);
346        let f = from_q16(q16);
347        assert!(
348            (f - 1.0).abs() < 1e-4,
349            "Q31->Q16 should be near 1.0, got {f}"
350        );
351    }
352
353    #[test]
354    fn mul_q16_basic() {
355        let a = to_q16(1.5);
356        let b = to_q16(2.0);
357        let result = from_q16(mul_q16(a, b));
358        assert!((result - 3.0).abs() < 1e-4, "1.5 * 2.0 = {result}");
359    }
360
361    #[test]
362    fn mul_q16_negative() {
363        let a = to_q16(-2.5);
364        let b = to_q16(4.0);
365        let result = from_q16(mul_q16(a, b));
366        assert!((result - (-10.0)).abs() < 1e-4, "-2.5 * 4.0 = {result}");
367    }
368
369    #[test]
370    fn mul_n_q16_integer_scale() {
371        let a = to_q16(3.5);
372        let result = from_q16(mul_n_q16(a, 4));
373        assert!((result - 14.0).abs() < 1e-4, "3.5 * 4 = {result}");
374    }
375
376    #[test]
377    fn mul_f_q16_float_scale() {
378        let a = to_q16(2.0);
379        let result = from_q16(mul_f_q16(a, 1.5));
380        assert!((result - 3.0).abs() < 1e-3, "2.0 * 1.5f = {result}");
381    }
382
383    #[test]
384    fn div_q16_basic() {
385        let result = from_q16(div_q16(to_q16(3.0), to_q16(2.0)));
386        assert!((result - 1.5).abs() < 1e-4, "3.0 / 2.0 = {result}");
387    }
388
389    #[test]
390    fn div_q16_zero_denominator() {
391        let result = div_q16(to_q16(5.0), 0);
392        assert_eq!(result, 0, "division by zero must return 0");
393    }
394
395    #[test]
396    fn div_n_q16_integer_divisor() {
397        let result = from_q16(div_n_q16(to_q16(9.0), 3));
398        assert!((result - 3.0).abs() < 1e-4, "9.0 / 3 = {result}");
399    }
400
401    #[test]
402    fn div_f_q16_float_divisor() {
403        let result = from_q16(div_f_q16(to_q16(6.0), 2.0));
404        assert!((result - 3.0).abs() < 1e-3, "6.0 / 2.0f = {result}");
405    }
406
407    #[test]
408    fn qadd_q16_no_overflow() {
409        assert_eq!(
410            from_q16(qadd_q16(to_q16(1.0), to_q16(2.0))).round() as i32,
411            3
412        );
413    }
414
415    #[test]
416    fn qadd_q16_saturates_at_max() {
417        let result = qadd_q16(Q16_MAX, Q16_MAX);
418        assert_eq!(result, Q16_MAX, "overflow must saturate at Q16_MAX");
419    }
420
421    #[test]
422    fn qsub_q16_saturates_at_min() {
423        let result = qsub_q16(Q16_MIN, Q16_MAX);
424        assert_eq!(result, Q16_MIN, "underflow must saturate at Q16_MIN");
425    }
426
427    #[test]
428    fn abs_q16_positive_unchanged() {
429        let v = to_q16(5.75);
430        assert_eq!(abs_q16(v), v);
431    }
432
433    #[test]
434    fn abs_q16_negated() {
435        let v = to_q16(-3.14);
436        let expected = to_q16(3.14);
437        assert_eq!(abs_q16(v), expected);
438    }
439
440    #[test]
441    fn lerp_q16_midpoint() {
442        let a = to_q16(0.0);
443        let b = to_q16(1.0);
444        let mid = from_q16(lerp_q16(a, b, 1, 2));
445        assert!((mid - 0.5).abs() < 1e-4, "lerp mid = {mid}");
446    }
447
448    #[test]
449    fn lerp_q16_endpoints() {
450        let a = to_q16(10.0);
451        let b = to_q16(20.0);
452        assert_eq!(lerp_q16(a, b, 0, 10), a, "t=0 must return left endpoint");
453        assert_eq!(
454            lerp_q16(a, b, 10, 10),
455            b,
456            "t=denom must return right endpoint"
457        );
458    }
459
460    #[test]
461    fn lerp_q16_zero_denom_returns_left() {
462        let a = to_q16(5.0);
463        let b = to_q16(9.0);
464        assert_eq!(lerp_q16(a, b, 0, 0), a, "zero denom must return left");
465    }
466
467    #[test]
468    fn angle_to_q16_90_degrees() {
469        let q = angle_to_q16(90.0);
470        let rad = from_q16(q);
471        assert!(
472            (rad - core::f32::consts::FRAC_PI_2).abs() < 1e-4,
473            "90 deg should be pi/2, got {rad}"
474        );
475    }
476
477    #[test]
478    fn angle_to_q16_360_degrees() {
479        let q = angle_to_q16(360.0);
480        let rad = from_q16(q);
481        assert!(
482            (rad - 2.0 * core::f32::consts::PI).abs() < 1e-4,
483            "360 deg should be 2*pi, got {rad}"
484        );
485    }
486
487    #[test]
488    fn recip_q16_one() {
489        let result = from_q16(recip_q16(FP_ONE));
490        assert!(
491            (result - 1.0).abs() < 0.01,
492            "recip(1.0) approx 1.0, got {result}"
493        );
494    }
495
496    #[test]
497    fn recip_q16_two() {
498        let result = from_q16(recip_q16(to_q16(2.0)));
499        assert!(
500            (result - 0.5).abs() < 0.01,
501            "recip(2.0) approx 0.5, got {result}"
502        );
503    }
504
505    #[test]
506    fn recip_q16_zero_returns_sentinel() {
507        assert_eq!(
508            recip_q16(0),
509            Q16_MAX,
510            "recip(0) must return Q16_MAX sentinel"
511        );
512    }
513
514    #[test]
515    fn scanline_interp_starts_at_left() {
516        let interp = ScanlineInterp::new(100, 200, 0, 65536, 0, 32768, 10);
517        assert_eq!(interp.z(), 100);
518        assert_eq!(interp.u(), 0);
519        assert_eq!(interp.v(), 0);
520    }
521
522    #[test]
523    fn scanline_interp_step_reaches_right() {
524        let mut interp = ScanlineInterp::depth_only(0, 65536, 10);
525        for _ in 0..10 {
526            interp.step();
527        }
528        let diff = (interp.z() as i64 - 65536i64).abs();
529        assert!(
530            diff <= 10,
531            "z after 10 steps should be within 10 of 65536, got {} (diff={})",
532            interp.z(),
533            diff
534        );
535    }
536
537    #[test]
538    fn scanline_interp_uv_reaches_right() {
539        let mut interp = ScanlineInterp::new(0, 0, 0, 65536, 0, 65536, 8);
540        for _ in 0..8 {
541            interp.step();
542        }
543        let u_err = (interp.u() as i64 - 65536i64).abs();
544        let v_err = (interp.v() as i64 - 65536i64).abs();
545        assert!(
546            u_err <= 1,
547            "u after 8 steps should be 65536 +/- 1, got {}",
548            interp.u()
549        );
550        assert!(
551            v_err <= 1,
552            "v after 8 steps should be 65536 +/- 1, got {}",
553            interp.v()
554        );
555    }
556
557    #[test]
558    fn scanline_interp_zero_span() {
559        let mut interp = ScanlineInterp::new(1000, 9999, 0, 65536, 0, 65536, 0);
560        interp.step();
561        interp.step();
562        assert_eq!(interp.z(), 1000, "zero-span z must not move");
563        assert_eq!(interp.u(), 0, "zero-span u must not move");
564    }
565
566    #[test]
567    fn scanline_interp_step_n() {
568        let mut interp = ScanlineInterp::depth_only(0, 100000, 10);
569        interp.step_n(5);
570        let expected = 50000u32;
571        let diff = (interp.z() as i64 - expected as i64).abs();
572        assert!(
573            diff <= 1,
574            "step_n(5) should land at 50000 +/- 1, got {}",
575            interp.z()
576        );
577    }
578
579    #[test]
580    fn scanline_interp_z_f32() {
581        let interp = ScanlineInterp::depth_only(65536, 65536, 0);
582        assert!((interp.z_f32() - 1.0).abs() < 1e-5, "z_f32 should be 1.0");
583    }
584}