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])[]): numberParameters
| Name | Description |
|---|---|
x | The input to look up. |
table | The 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); // 380Tests
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