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.


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.


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.


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).


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.


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.


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)


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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.