Skip to main content

pcb_toolkit/tables/
interpolate.rs

1//! Generic linear interpolation for lookup tables.
2
3/// Linear interpolation in a sorted table of (x, y) pairs.
4///
5/// If `x` is below the first entry, returns the first y value.
6/// If `x` is above the last entry, returns the last y value.
7/// Otherwise, linearly interpolates between the two surrounding entries.
8///
9/// # Panics
10///
11/// Panics if `table` is empty.
12pub fn lerp(table: &[(f64, f64)], x: f64) -> f64 {
13    assert!(!table.is_empty(), "interpolation table must not be empty");
14
15    if table.len() == 1 || x <= table[0].0 {
16        return table[0].1;
17    }
18    if x >= table[table.len() - 1].0 {
19        return table[table.len() - 1].1;
20    }
21
22    // Binary search for the interval containing x
23    let i = match table.binary_search_by(|entry| entry.0.partial_cmp(&x).unwrap()) {
24        Ok(i) => return table[i].1, // exact match
25        Err(i) => i - 1,
26    };
27
28    let (x0, y0) = table[i];
29    let (x1, y1) = table[i + 1];
30    let t = (x - x0) / (x1 - x0);
31    y0 + t * (y1 - y0)
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37
38    #[test]
39    fn exact_match() {
40        let table = &[(1.0, 10.0), (2.0, 20.0), (3.0, 30.0)];
41        assert!((lerp(table, 2.0) - 20.0).abs() < 1e-10);
42    }
43
44    #[test]
45    fn midpoint_interpolation() {
46        let table = &[(0.0, 0.0), (10.0, 100.0)];
47        assert!((lerp(table, 5.0) - 50.0).abs() < 1e-10);
48    }
49
50    #[test]
51    fn clamp_below() {
52        let table = &[(1.0, 10.0), (2.0, 20.0)];
53        assert!((lerp(table, -5.0) - 10.0).abs() < 1e-10);
54    }
55
56    #[test]
57    fn clamp_above() {
58        let table = &[(1.0, 10.0), (2.0, 20.0)];
59        assert!((lerp(table, 99.0) - 20.0).abs() < 1e-10);
60    }
61}