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