josh oertel

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][0, 255]. You want to take a sample and map it to the range [1,1][-1, 1]. The [1,1][-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][0, 255] to a number in the range [1,1][-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][a, b] to the range [c,d][c, d].

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.

[a,b]a=[aa,ba]=[0,ba]\begin{align*} [a, b] - a &= [a-a, b-a]\\ &= [0, b-a] \end{align*}

normalize

The next step is to normalize to the range [0,1][0, 1]. This can be achieved by dividing by the difference between b and a.

[0,ba](ba)=[0ba,baba]=[0,1]\begin{align*} \frac{[0, b - a]}{(b-a)} &= \left[\frac{0}{b-a}, \frac{b-a}{b-a}\right]\\ &= [0, 1] \end{align*}

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 [a,b][a, b] where b=ab = a is the same as [a,a][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.

scale to output range

Normalizing in the previous step got us to the range [0,1][0, 1]. We kinda do the inverse of the first two steps to get to the range [c,d][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.

[0,1](dc)=[0(dc),1(dc)]=[0,dc]\begin{align*} [0, 1] * (d-c) &= [0 * (d-c), 1 * (d-c)]\\ &= [0, d-c] \end{align*}

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.

[0,dc]+c=[0+c,dc+c]=[c,d]\begin{align*} [0, d-c] + c &= [0+c, d-c+c]\\ &= [c, d] \end{align*}

combining it all into a function

The four steps above can be written as a single function.

map.ts
/**
* 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.

example.ts
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.

resources