All files / math interpolate-table.ts

100% Statements 20/20
100% Branches 10/10
100% Functions 1/1
100% Lines 20/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43                          11x 11x 11x 1x   10x 2x   8x 3x   5x 5x 5x 5x 5x 17x     17x 9x 9x   8x 8x     5x    
/**
 * Reads a lookup table with linear interpolation between its points, such as a calibration curve or a tank
 * gauging table. Outside the table, the first or last output is held. Binary search: O(log n).
 *
 * @param x - The input to look up.
 * @param table - The points `[x, y]`, sorted by increasing `x`.
 * @returns The interpolated output; `NaN` for an empty table.
 * @example
 * const tank = [[0, 0], [10, 150], [20, 380]] as const;
 * interpolateTable(15, tank); // 265
 * interpolateTable(40, tank); // 380
 */
export function interpolateTable(x: number, table: readonly (readonly [x: number, y: number])[]): number {
  const [first] = table;
  const last = table.at(-1);
  if (first === undefined || last === undefined) {
    return NaN;
  }
  if (x <= first[0]) {
    return first[1];
  }
  if (x >= last[0]) {
    return last[1];
  }
  let low = first;
  let high = last;
  let lowIndex = 0;
  let highIndex = table.length - 1;
  while (highIndex - lowIndex > 1) {
    const middleIndex = (lowIndex + highIndex) >>> 1;
    /* v8 ignore next -- `?? high` only satisfies noUncheckedIndexedAccess. */
    const middle = table[middleIndex] ?? high;
    if (middle[0] <= x) {
      low = middle;
      lowIndex = middleIndex;
    } else {
      high = middle;
      highIndex = middleIndex;
    }
  }
  return low[1] + ((high[1] - low[1]) * (x - low[0])) / (high[0] - low[0]);
}