deriving the function for mapping from one range to another
This problem comes up in various math and computer science topics. For example, let’s say you’re working with frequency data of a song. Each sample is a value that fits into a byte and ranges from 0 to 255. The range in interval notation would be written . You want to take a sample and map it to the range . The range is actually pretty common and can be found in such fields as graphics programming and trigonometry.
This is a bit of a contrived example but nevertheless the goal is to map a number in the range to a number in the range . We want the mapping to be proportionate such that 0 maps to -1, 255 maps to 1, and everything in-between maps linearly. We’re looking for a formula for such a transformation from any input range to any output range, in other words, from the range to the range .
translate to the origin
The first step is to translate the input range so that the low-end starts at 0. This isn’t needed in the example above since the low end of input range is already 0 ,but it’s a step that can’t be skipped. It’ll become clear in the next steps why this translation needs to come first.
normalize
The next step is to normalize to the range . This can be achieved by dividing by the difference between b and a.
A consequence of this is that a and b must not be the same because then the denominator would equal 0, and you’d be dividing by 0.
If one we’re dealing with such a range, You’d only be mapping one number to a range, and it would be ambiguous where in the range that number would fall. The range where is the same as which is just the number a. Does a get mapped to the low end, the high end, or somewhere in-between? There’s no b to determine where a should map to.
scale to output range
Normalizing in the previous step got us to the range . We kinda do the inverse of the first two steps to get to the range . First we scale by d - c. Since the range right now is normalized, this operation won’t affect the lower end of the range.
The goal is to have d on the high-end and c on the low-end. Therefore, all we need to do now is shift by c.
translate to the start of the output range
Here all we need to do is add c.
combining it all into a function
The four steps above can be written as a single function.
/** * returns a function that linearly maps a number in the input range [a, b] to the output range [c, d] */export default (a: number, b: number, c: number, d: number) => { return (n: number): number => { return ((n - a) / (b - a)) * (d - c) + c; };};In practice, solving the problem from the beginning of the post would look like the following.
import map from "./map";import { midpoint } from "$lib/utils/math";
const a = 0;const b = 255;const c = -1;const d = 1;
const halfway = midpoint(a, b);
const inputs = [a, halfway, b];
const m = map(a, b, c, d);
const outputs: number[] = [];for (const input of inputs) { const output = m(input); outputs.push(output);}
// outputs === [-1, 0, 1]all done
map is a really simple function, but it’s not one that can easily be understood
unless you think about it geometrically. I first saw this function when I used
to program on the Arduino way back when. It never really clicked for me until I
started graphics programming and thought about it visually. Mapping between
different coordinate systems is a very common activity in graphics. I guess that
goes to show how ubiquitous map is.