Math - mappage des nombres

Réponses:

210

Si votre nombre X se situe entre A et B, et que vous souhaitez que Y tombe entre C et D, vous pouvez appliquer la transformation linéaire suivante:

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

Cela devrait vous donner ce que vous voulez, bien que votre question soit un peu ambiguë, car vous pouvez également mapper l'intervalle dans le sens inverse. Faites juste attention à la division par zéro et vous devriez être OK.

PeterAllenWebb
la source
47
Ensuite, peut-être marquer cette réponse comme «acceptée» en cliquant sur la coche à côté.
Konrad Rudolph
16
Pour plus de clarté, j'aime new_value = (old_value - old_bottom) / (old_top - old_bottom) * (new_top - new_bottom) + new_bottom;
ftrotter le
1
Y a-t-il une dérivation pour cette équation quelque part?
shaveenk
@shaveenk cela devrait être l'équation d'une ligne, avec Y=f(X)=m*X+b, où m et b ont été déterminés simultanément à partir des deux équations de contraintes suivantes qui résultent de la substitution des valeurs de X et Y aux points d'extrémité requis: C=m*A+betD=m*B+b
Chris Chiasson
J'ai aussi fini par avoir besoin d'utiliser X=A+(A-B)*tpour prouver l'égalité entre cette approche et celle de Peter. t est essentiellement une non dimensionnalisation de X. ( t=(X-A)/(A-B))
Chris Chiasson
21

Divisez pour obtenir le rapport entre les tailles des deux plages, puis soustrayez la valeur de départ de votre plage initiale, multipliez par le rapport et ajoutez la valeur de départ de votre deuxième plage. En d'autres termes,

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

Cela répartit uniformément les nombres de la première plage dans la seconde plage.

Konrad Rudolph
la source
Cela ne marche pas. Ma plage va de 1000000000 à 9999999999 et les nombres pourraient être de 1 à 999999999.
Dejell
@Odelya Bien sûr, cela fonctionne. C'est une transformation mathématique assez simple. Il vous suffit d'utiliser un type de nombre suffisamment grand (bignum ou similaire). Vos nombres sont tout simplement trop gros pour les entiers 32 bits - mais les entiers 64 bits par exemple fonctionneront.
Konrad Rudolph
Ils sont de type double. double R = (20 - 10) / (6 - 2); double y = (X - 2) * R + 10;
Dejell
@Odelya Même problème. Vous devriez lire sur la précision en virgule flottante. En fait, il est nécessaire de lire: Ce que tout informaticien devrait savoir sur l'arithmétique à virgule flottante - si vous avez besoin d'un type à virgule flottante avec des nombres aussi grands, vous devrez peut-être utiliser un type de nombre à précision arbitraire .
Konrad Rudolph
Pouvez-vous recommander un type Java que je peux faire?
Dejell
7

Ce serait bien d'avoir cette fonctionnalité dans la java.lang.Mathclasse, car c'est une fonction si largement requise et disponible dans d'autres langues. Voici une implémentation simple:

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

Je mets ce code ici comme référence pour l'avenir moi-même et cela aidera peut-être quelqu'un.

Sourabh Bhat
la source
4

En passant, c'est le même problème que le classique convertir celcius en farenheit où vous voulez mapper une plage de nombres qui équivaut à 0 - 100 (C) à 32 - 212 (F).

Métro
la source
Comment est-ce une réponse?
shinzou
C'est un exemple de l'application de la question. Beaucoup ont ce problème simple dans les classes d'introduction CS et ne considèrent pas que la solution peut être généralisée à d'autres problèmes. J'essayais d'ajouter du contexte à la question initiale. La question initiale avait déjà reçu une réponse adéquate.
Metro
1

Chaque intervalle unitaire sur la première plage occupe (dc) / (ba) "espace" sur la seconde plage.

Pseudo:

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

La manière dont vous gérez les arrondis dépend de vous.

Chris Cudmore
la source
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);

Avec des contrôles sur la division par zéro, bien sûr.

PhiLho
la source
1

si votre plage de [a à b] et que vous voulez la mapper en [c à d] où x est la valeur que vous voulez mapper, utilisez cette formule (mappage linéaire)

double R = (d-c)/(b-a)
double y = c+(x*R)+R
return(y)
Mohamed Ashraf
la source
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
la source
0

En plus de la réponse @PeterAllenWebb, si vous souhaitez inverser le résultat, utilisez ce qui suit:

reverseX = (B-A)*(Y-C)/(D-C) + A
Dejell
la source