Skip to content

interpolateTable function ​

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).

ts
import { interpolateTable } from 'utils';

Signature ​

ts
export function interpolateTable(x: number, table: readonly (readonly [x: number, y: number])[]): number

Parameters ​

NameDescription
xThe input to look up.
tableThe points [x, y], sorted by increasing x.

Returns ​

The interpolated output; NaN for an empty table.

Example ​

ts
const tank = [[0, 0], [10, 150], [20, 380]] as const;
interpolateTable(15, tank); // 265
interpolateTable(40, tank); // 380

Tests ​

Line coverage: 100 %.

  • ✅ reads 15 as 265
  • ✅ reads 10 as 150
  • ✅ reads 25 as 490
  • ✅ reads 0 as 0
  • ✅ reads 30 as 600
  • ✅ reads -5 as 0
  • ✅ reads 40 as 600
  • ✅ reads a large table
  • ✅ handles a single point and duplicate inputs
  • ✅ returns NaN for an empty table

Source ​

src/math/interpolate-table.ts (on GitHub)

Source code
ts
/**
 * 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]);
}
Test file
ts
import { interpolateTable } from './interpolate-table';

const TANK = [
  [0, 0],
  [10, 150],
  [20, 380],
  [30, 600],
] as const;

describe(interpolateTable, () => {
  it.for([
    [15, 265],
    [10, 150],
    [25, 490],
    [0, 0],
    [30, 600],
    [-5, 0],
    [40, 600],
  ] as const)('reads %s as %s', ([x, expected]) => {
    expect(interpolateTable(x, TANK)).toBe(expected);
  });

  it('reads a large table', () => {
    const table = Array.from({ length: 1001 }, (_, index) => [index, index * 2] as const);
    expect(interpolateTable(512.25, table)).toBe(1024.5);
  });

  it('handles a single point and duplicate inputs', () => {
    expect(interpolateTable(5, [[1, 7]])).toBe(7);
    expect(
      interpolateTable(1.5, [
        [1, 10],
        [1, 20],
        [2, 30],
      ]),
    ).toBe(25);
  });

  it('returns NaN for an empty table', () => {
    expect(interpolateTable(1, [])).toBeNaN();
  });
});

Keywords: interpolate, table, math, function

Generated from the JSDoc of the sources.