116

How do I map numbers, linearly, between a and b to go between c and d.

That is, I want numbers between 2 and 6 to map to numbers between 10 and 20... but I need the generalized case.

My brain is fried.

Luuklag
  • 3,897
  • 11
  • 38
  • 57
Sam
  • 6,167
  • 6
  • 26
  • 24

10 Answers10

238

If your number X falls between A and B, and you would like Y to fall between C and D, you can apply the following linear transform:

Y = (X-A)/(B-A) * (D-C) + C

That should give you what you want, although your question is a little ambiguous, since you could also map the interval in the reverse direction. Just watch out for division by zero and you should be OK.

Markus Dresch
  • 5,290
  • 3
  • 20
  • 40
PeterAllenWebb
  • 10,319
  • 3
  • 37
  • 44
  • 21
    For clarity, I like new_value = (old_value - old_bottom) / (old_top - old_bottom) * (new_top - new_bottom) + new_bottom; – ftrotter Jun 02 '14 at 04:25
  • 2
    Is there a derivation for this equation somewhere? – shaveenk Oct 28 '16 at 21:39
  • 1
    @shaveenk it should be the equation of a line, with `Y=f(X)=m*X+b`, where m and b have been determined simultaneously from the following two constraint equations that result from substituting the values of X and Y at the required endpoints: `C=m*A+b` and `D=m*B+b` – Chris Chiasson Mar 09 '17 at 16:57
  • I also ended up needing to use `X=A+(A-B)*t` to prove the equality between this approach and Peter's. t is essentially a nondimensionalization of X. (`t=(X-A)/(A-B)`) – Chris Chiasson Mar 09 '17 at 18:32
  • 2
    To reverse the direction, the formula is ( (X-A)/(A-B) * (C-D) ) * -1 + D – Corey Levinson Dec 02 '18 at 20:59
23

Divide to get the ratio between the sizes of the two ranges, then subtract the starting value of your inital range, multiply by the ratio and add the starting value of your second range. In other words,

R = (20 - 10) / (6 - 2)
y = (x - 2) * R + 10

This evenly spreads the numbers from the first range in the second range.

Adi
  • 5,089
  • 6
  • 33
  • 47
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • This doesn't work. My range is 1000000000 to 9999999999 and the numbers could be from 1 to 999999999. – Dejell Feb 19 '13 at 09:39
  • @Odelya Of course it works. It’s a simple enough mathematical transformation. You just need to use a big enough number type (bignum or similar). Your numbers are simply too big for 32 bit integers – but 64 bit integers for instance will work. – Konrad Rudolph Feb 19 '13 at 11:40
  • They are of type double. double R = (20 - 10) / (6 - 2); double y = (X - 2) * R + 10; – Dejell Feb 19 '13 at 11:43
  • @Odelya Same problem though. You should read up on floating-point precision. In fact, this is required reading: [What Every Computer Scientist Should Know About Floating-Point Arithmetic](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) – if you need a floating-point type with such big numbers you might have to use an [arbitrary-precision number type](http://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic#Libraries). – Konrad Rudolph Feb 19 '13 at 11:52
  • Can you recommend of a java type that I can do it? – Dejell Feb 20 '13 at 10:02
  • @Odelya The article I linked mentions [the relevant Java class](http://docs.oracle.com/javase/6/docs/api/java/math/BigDecimal.html) … – Konrad Rudolph Feb 20 '13 at 10:12
9

It would be nice to have this functionality in the java.lang.Math class, as this is such a widely required function and is available in other languages. Here is a simple implementation:

final static double EPSILON = 1e-12;

public static double map(double valueCoord1,
        double startCoord1, double endCoord1,
        double startCoord2, double endCoord2) {

    if (Math.abs(endCoord1 - startCoord1) < EPSILON) {
        throw new ArithmeticException("/ 0");
    }

    double offset = startCoord2;
    double ratio = (endCoord2 - startCoord2) / (endCoord1 - startCoord1);
    return ratio * (valueCoord1 - startCoord1) + offset;
}

I am putting this code here as a reference for future myself and may be it will help someone.

Sourabh Bhat
  • 1,793
  • 16
  • 21
3

As an aside, this is the same problem as the classic convert celcius to farenheit where you want to map a number range that equates 0 - 100 (C) to 32 - 212 (F).

Metro
  • 1,464
  • 14
  • 24
  • How is this an answer? – shinzou Oct 26 '17 at 16:38
  • 1
    It is an example of the application of the question. Many have this simple problem in introductory CS classes and do not consider that the solution can be generalized to other problems. I was trying to add context to the original question. The original question had already been adequately answered. – Metro Dec 07 '17 at 20:21
3

https://rosettacode.org/wiki/Map_range

[a1, a2] => [b1, b2]

if s in range of [a1, a2]

then t which will be in range of [b1, b2]

t= b1 + ((s- a1) * (b2-b1))/ (a2-a1)
Amerrnath
  • 2,227
  • 4
  • 22
  • 33
2

In addition to @PeterAllenWebb answer, if you would like to reverse back the result use the following:

reverseX = (B-A)*(Y-C)/(D-C) + A
Dejell
  • 13,947
  • 40
  • 146
  • 229
1

Each unit interval on the first range takes up (d-c)/(b-a) "space" on the second range.

Pseudo:

var interval = (d-c)/(b-a)
for n = 0 to (b - a)
    print c + n*interval

How you handle the rounding is up to you.

Chris Cudmore
  • 29,793
  • 12
  • 57
  • 94
1

if your range from [a to b] and you want to map it in [c to d] where x is the value you want to map use this formula (linear mapping)

double R = (d-c)/(b-a)
double y = c+(x*R)+R
return(y)
1

Where X is the number to map from A-B to C-D, and Y is the result: Take the linear interpolation formula, lerp(a,b,m)=a+(m*(b-a)), and put C and D in place of a and b to get Y=C+(m*(D-C)). Then, in place of m, put (X-A)/(B-A) to get Y=C+(((X-A)/(B-A))*(D-C)). This is an okay map function, but it can be simplified. Take the (D-C) piece, and put it inside the dividend to get Y=C+(((X-A)*(D-C))/(B-A)). This gives us another piece we can simplify, (X-A)*(D-C), which equates to (X*D)-(X*C)-(A*D)+(A*C). Pop that in, and you get Y=C+(((X*D)-(X*C)-(A*D)+(A*C))/(B-A)). The next thing you need to do is add in the +C bit. To do that, you multiply C by (B-A) to get ((B*C)-(A*C)), and move it into the dividend to get Y=(((X*D)-(X*C)-(A*D)+(A*C)+(B*C)-(A*C))/(B-A)). This is redundant, containing both a +(A*C) and a -(A*C), which cancel each other out. Remove them, and you get a final result of: Y=((X*D)-(X*C)-(A*D)+(B*C))/(B-A)

TL;DR: The standard map function, Y=C+(((X-A)/(B-A))*(D-C)), can be simplified down to Y=((X*D)-(X*C)-(A*D)+(B*C))/(B-A)

JMoore2007
  • 51
  • 4
0
int srcMin = 2, srcMax = 6;
int tgtMin = 10, tgtMax = 20;

int nb = srcMax - srcMin;
int range = tgtMax - tgtMin;
float rate = (float) range / (float) nb;

println(srcMin + " > " + tgtMin);
float stepF = tgtMin;
for (int i = 1; i < nb; i++)
{
  stepF += rate;
  println((srcMin + i) + " > " + (int) (stepF + 0.5) + " (" + stepF + ")");
}
println(srcMax + " > " + tgtMax);

With checks on divide by zero, of course.

PhiLho
  • 40,535
  • 6
  • 96
  • 134