Matematyka - mapowanie liczb

Odpowiedzi:

211

Jeśli twoja liczba X mieści się między A i B, a chciałbyś, aby Y znajdował się między C i D, możesz zastosować następującą transformację liniową:

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

To powinno dać ci to, czego chcesz, chociaż twoje pytanie jest trochę niejednoznaczne, ponieważ możesz również odwzorować interwał w odwrotnym kierunku. Uważaj tylko na dzielenie przez zero i powinno być OK.

PeterAllenWebb
źródło
16
Dla jasności lubię new_value = (old_value - old_bottom) / (old_top - old_bottom) * (new_top - new_bottom) + new_bottom;
ftrotter
1
Czy jest gdzieś wyprowadzenie tego równania?
shaveenk
@shaveenk powinno to być równanie prostej, Y=f(X)=m*X+bgdzie m i b zostały wyznaczone jednocześnie z następujących dwóch równań ograniczających, które wynikają z podstawienia wartości X i Y w wymaganych punktach końcowych: C=m*A+borazD=m*B+b
Chris Chiasson
Skończyło się również na tym, że musiałem X=A+(A-B)*tudowodnić równość między tym podejściem a podejściem Petera. t jest zasadniczo niewymiarowaniem X. ( t=(X-A)/(A-B))
Chris Chiasson
Aby odwrócić kierunek, wzór to ((XA) / (AB) * (CD)) * -1 + D
Corey Levinson
21

Podziel, aby uzyskać stosunek między rozmiarami dwóch zakresów, a następnie odejmij wartość początkową zakresu początkowego, pomnóż przez współczynnik i dodaj wartość początkową drugiego zakresu. Innymi słowy,

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

Spowoduje to równomierne rozłożenie liczb z pierwszego zakresu w drugim zakresie.

Konrad Rudolph
źródło
To nie działa. Mój zakres to 1000000000 do 9999999999, a liczby mogą wynosić od 1 do 999999999.
Dejell,
@Odelya Oczywiście, że działa. To dość prosta transformacja matematyczna. Wystarczy użyć odpowiednio dużego typu liczbowego (bignum lub podobny). Twoje liczby są po prostu za duże dla 32-bitowych liczb całkowitych - ale na przykład 64-bitowe liczby całkowite będą działać.
Konrad Rudolph
Są typu double. podwójne R = (20 - 10) / (6 - 2); podwójny y = (X - 2) * R + 10;
Dejell
@Odelya Jednak ten sam problem. Powinieneś przeczytać o precyzji zmiennoprzecinkowej. W rzeczywistości jest to wymagana lektura: Co każdy informatyk powinien wiedzieć o arytmetyce zmiennoprzecinkowej - jeśli potrzebujesz typu zmiennoprzecinkowego z tak dużymi liczbami, być może będziesz musiał użyć typu liczbowego o dowolnej precyzji .
Konrad Rudolph
Czy możesz polecić java, że ​​mogę to zrobić?
Dejell
7

Byłoby miło mieć tę funkcjonalność w java.lang.Mathklasie, ponieważ jest to bardzo wymagana funkcja i jest dostępna w innych językach. Oto prosta implementacja:

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;
}

Umieszczam ten kod tutaj jako odniesienie na przyszłość i może to komuś pomoże.

Sourabh Bhat
źródło
4

Na marginesie, jest to ten sam problem, co w klasycznej konwersji Celsjusza na Farenheita, w którym chcesz zmapować zakres liczb, który jest równy 0 - 100 (C) do 32 - 212 (F).

Metro
źródło
Jak to jest odpowiedź?
shinzou
To jest przykład zastosowania pytania. Wielu ma ten prosty problem we wprowadzających klasach CS i nie uważa, że ​​rozwiązanie można uogólnić na inne problemy. Próbowałem nadać kontekst pierwotnemu pytaniu. Na pierwotne pytanie udzielono już wystarczającej odpowiedzi.
Metro
1

Każdy odstęp jednostkowy w pierwszym zakresie zajmuje (dc) / (ba) „spację” w drugim zakresie.

Rzekomy:

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

Sposób obsługi zaokrąglania zależy od Ciebie.

Chris Cudmore
źródło
1
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);

Oczywiście z kontrolą dzielenia przez zero.

PhiLho
źródło
1

jeśli twój zakres od [a do b] i chcesz go zmapować w [c do d], gdzie x jest wartością, którą chcesz odwzorować, użyj tego wzoru (mapowanie liniowe)

double R = (d-c)/(b-a)
double y = c+(x*R)+R
return(y)
Mohamed Ashraf
źródło
1

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
źródło
0

Oprócz odpowiedzi @PeterAllenWebb, jeśli chcesz cofnąć wynik, użyj następującego:

reverseX = (B-A)*(Y-C)/(D-C) + A
Dejell
źródło