Skip to main content

sidereon_core/astro/math/
interp.rs

1//! Scalar linear-interpolation primitives with pinned operation order.
2//!
3//! GNSS interpolators are bit-exact against deployed references, so the two
4//! distinct evaluation orders below are kept as separate named helpers rather
5//! than folded into one: callers that precompute a fraction need
6//! divide-before-multiply, while clock interpolators pin multiply-before-divide.
7
8/// Linear interpolation by a precomputed fraction: `a + (b - a) * t`.
9///
10/// `t` is the interpolation parameter (`0.0` returns `a`, `1.0` returns `b`).
11/// The caller is responsible for forming `t`; this helper fixes only the
12/// `a + (b - a) * t` evaluation order.
13#[inline]
14pub fn lerp(a: f64, b: f64, t: f64) -> f64 {
15    a + (b - a) * t
16}
17
18/// Linear interpolation by an explicit ratio, multiplying before dividing:
19/// `a + (b - a) * num / den`.
20///
21/// This is `lerp` with `t = num / den` but with the division applied last, which
22/// is a different floating-point rounding than `lerp(a, b, num / den)`. The
23/// clock interpolators pin this order for bit-exact parity, so it has its own
24/// helper. The caller guarantees `den` is nonzero.
25#[inline]
26pub fn lerp_ratio(a: f64, b: f64, num: f64, den: f64) -> f64 {
27    a + (b - a) * num / den
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33
34    #[test]
35    fn lerp_matches_explicit_recipe_bits() {
36        let (a, b, t) = (1.25_f64, -3.5_f64, 0.3_f64);
37        assert_eq!(lerp(a, b, t).to_bits(), (a + (b - a) * t).to_bits());
38    }
39
40    #[test]
41    fn lerp_endpoints_are_exact() {
42        assert_eq!(lerp(2.0, 5.0, 0.0).to_bits(), 2.0_f64.to_bits());
43        assert_eq!(lerp(2.0, 5.0, 1.0).to_bits(), 5.0_f64.to_bits());
44    }
45
46    #[test]
47    fn lerp_ratio_matches_explicit_recipe_bits() {
48        let (a, b, num, den) = (1.0e-6_f64, 1.3e-6_f64, 7.0_f64, 30.0_f64);
49        assert_eq!(
50            lerp_ratio(a, b, num, den).to_bits(),
51            (a + (b - a) * num / den).to_bits()
52        );
53    }
54
55    #[test]
56    fn lerp_ratio_preserves_multiply_before_divide_order() {
57        // A case where multiply-before-divide and divide-before-multiply round
58        // differently, so the helper is pinned to the former.
59        let (a, b, num, den) = (0.0_f64, 1.0_f64, 1.0_f64, 3.0_f64);
60        assert_eq!(
61            lerp_ratio(a, b, num, den).to_bits(),
62            (a + (b - a) * num / den).to_bits()
63        );
64    }
65}