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 [0, 255]. You want to take a sample and map it to the range [-1, 1]. The [-1, 1] 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 [0, 255] to a number in the range [-1, 1]. 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 [a, b] to the range [c, d].
translate to the origin
The first step is to slide 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.
The range [a, b] maps to the range [a - a, b - a] equals [0, b - a]. From the example in the opening, [0, 255] becomes [0 - 0, 255 - 0].
normalize
The next step is to normalize to the range [0, 1]. This can be achieved by dividing by the difference between b and a. This will map the range [0, b] to the range [0 / (b - a), b / (b - a)] or [0, b / (b - a)].
A consequence of this is that a and b must not be the same because b - a 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 [a, b] where b = a is the same as [a, a] 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.
Following along, the range [0, 255] maps to [0 / (255 - 0), 255 / (255 - 0)] or [0, 1].
scale to output range
Normalizing in the previous step got us to the range [0, 1]. We kinda do the inverse of the first two steps to get to the range [c, d]. First we scale by d - c. Since the range right now is normalized, this operation won’t affect the lower end of the range. We go from the range [0, 1] to the range [0 * (d - c), 1 * (d - c)] or [0, d - c].
Following the example, [0, 1] goes to [0 * (1 - (-1)), 1 * (1 - (-1))] equals [0 * 2, 1 * 2] equals [0, 2].
translate to the start of the output range
The final step is to map from [0, d - c] to [c, d]. Here all we need to do is add c. [0 + c, d - c + c] equals [c, d].
From the example, [0, 2] maps to [0 + (-1), 2 + (-1)] equals [-1, 1].
combining it all into a function
The four steps above can be written as a single function.
const map = (
inLow: number,
inHigh: number,
outLow: number,
outHigh: number,
) => {
return (n: 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.
const inLow = 0;
const inHigh = 255;
const outLow = -1;
const outHigh = 1;
const halfway = 0.5 * (inLow + inHigh);
const inputs = [inLow, halfway, inHigh];
const m = map(inLow, inHigh, outLow, outHigh);
const outputs: number[] = [];
for (const input of inputs) {
outputs.push(m(input));
}
console.log(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.