128 votes

Mappage d'une plage numérique sur une autre

Les mathématiques n'ont jamais été mon point fort à l'école :(

int input_start = 0;    // The lowest number of the range input.
int input_end = 254;    // The largest number of the range input.
int output_start = 500; // The lowest number of the range output.
int output_end = 5500;  // The largest number of the range output.

int input = 127; // Input value.
int output = 0;

Comment puis-je convertir la valeur d'entrée en la valeur de sortie correspondante de cette plage ?

Par exemple, une valeur d'entrée de "0" correspondrait à une valeur de sortie de "500", une valeur d'entrée de "254" correspondrait à une valeur de sortie de "5500". Je n'arrive pas à comprendre comment calculer une valeur de sortie si une valeur d'entrée est, par exemple, 50 ou 101.

Je suis sûr que c'est simple, mais je n'arrive pas à réfléchir pour l'instant :)

Edit : Je n'ai besoin que de nombres entiers, pas de fractions ou quoi que ce soit d'autre.

262voto

Alok Singhal Points 33073

Oublions les mathématiques et essayons de résoudre ce problème de manière intuitive.

Tout d'abord, si nous voulons faire correspondre des nombres d'entrée dans l'intervalle [ 0 , x pour la plage de sortie [ 0 , y Il suffit d'augmenter l'échelle d'un montant approprié. 0 va à 0, x se rend à y et un nombre t ira à (y/x)*t .

Réduisons donc votre problème au problème plus simple ci-dessus.

Une plage d'entrée de [ input_start , input_end ] a input_end - input_start + 1 chiffres. C'est donc l'équivalent d'une plage de [ 0 , r ], où r = input_end - input_start .

De même, la plage de sortie est équivalente à [ 0 , R ], où R = output_end - output_start .

Une entrée de input est équivalent à x = input - input_start . Ce qui, à partir du premier paragraphe, se traduira par y = (R/r)*x . Ensuite, nous pouvons traduire le y à la plage de sortie d'origine en ajoutant output_start : output = output_start + y .

Cela nous donne :

output = output_start + ((output_end - output_start) / (input_end - input_start)) * (input - input_start)

Ou, d'une autre manière :

/* Note, "slope" below is a constant for given numbers, so if you are calculating
   a lot of output values, it makes sense to calculate it once.  It also makes
   understanding the code easier */
slope = (output_end - output_start) / (input_end - input_start)
output = output_start + slope * (input - input_start)

Comme il s'agit de C et que la division en C est tronquée, vous devriez essayer d'obtenir une réponse plus précise en calculant les choses en virgule flottante :

double slope = 1.0 * (output_end - output_start) / (input_end - input_start)
output = output_start + slope * (input - input_start)

Si l'on voulait être encore plus correct, il faudrait arrondir les chiffres au lieu de les tronquer dans la dernière étape. Vous pouvez le faire en écrivant un simple round fonction :

#include <math.h>
double round(double d)
{
    return floor(d + 0.5);
}

Ensuite :

output = output_start + round(slope * (input - input_start))

44voto

Dustin Points 35205

Arduino intègre cette fonction sous la forme carte .

Exemple :

/* Map an analog value to 8 bits (0 to 255) */
void setup() {}

void loop()
{
  int val = analogRead(0);
  val = map(val, 0, 1023, 0, 255);
  analogWrite(9, val);
}

La mise en œuvre figure également sur cette page :

long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

28voto

Erti-Chris Eelmaa Points 5165

La formule est la suivante

f(x) = (x - input_start) / (input_end - input_start) * (output_end - début_de_sortie) + début_de_sortie

Je mettrai ce message en ligne ici : https://betterexplained.com/articles/rethinking-arithmetic-a-visual-guide/ car il m'a beaucoup aidé lorsque j'ai essayé de trouver cette solution de manière intuitive. Une fois que vous avez compris ce que dit le billet, il est facile de trouver ces formules par vous-même. Notez que j'ai également eu du mal à répondre à ce genre de questions. (Je n'ai aucune affiliation - j'ai juste trouvé cela très utile)

Dites que vous avez une portée [input_start..input_end] Commençons par le normaliser de telle sorte que 0 soit input_start et 1 est input_end Il s'agit d'une technique simple pour faciliter le problème.

Comment faire ? Il faut tout décaler vers la gauche de la quantité input_start, de telle sorte que si l'entrée x se trouve être input_start il devrait donner zéro.

Ainsi, disons que f(x) est la fonction qui effectue la conversion.

f(x) = x - input_start

essayons-le :

f(input_start) = input_start - input_start = 0

travaille pour input_start .

à ce stade, il ne fonctionne pas pour les input_end pour l'instant, car nous ne l'avons pas mis à l'échelle.

Il suffit de réduire l'échelle en fonction de la longueur de la plage, de sorte que la plus grande valeur (input_end) soit associée à un.

f(x) = (x - input_start) / (input_end - input_start)

ok, essayons avec input_end .

f (input_end) = (input_end - input_start) / (input_end - input_start) = 1

Génial, ça a l'air de marcher.

Ok, prochaine étape, nous allons le mettre à l'échelle de la plage de sortie. C'est aussi simple que de multiplier par la longueur réelle de la plage de sortie, comme telle :

f(x) = (x - input_start) / (input_end - input_start) * (output_end - output_start)

maintenant, en fait, nous avons presque fini, nous devons juste le décaler vers la droite pour que 0 commence à partir de output_start.

f(x) = (x - input_start) / (input_end - input_start) * (output_end - output_start) + output_start

Faisons un essai rapide.

f(input_start) = (input_start - input_start) / (input_end - input_start) * (output_end - output_start) + output_start

vous voyez que la première partie de l'équation est pratiquement multipliée par zéro, ce qui annule tout, et vous donne

f(input_start) = output_start

essayons input_end également.

f(input_end) = (input_end - input_start) / (input_end - input_start) * (output_end - output_start) + output_start

qui, à son tour, aboutira à :

f(input_end) = output_end - output_start + output_start = output_end

comme vous pouvez le voir, il semble maintenant être mappé correctement.

17voto

Sven Marnach Points 133943

Le point crucial ici est d'effectuer la division entière (qui inclut l'arrondi) au bon endroit. Jusqu'à présent, aucune des réponses n'a correctement placé les parenthèses. Voici la bonne méthode :

int input_range = input_end - input_start;
int output_range = output_end - output_start;

output = (input - input_start)*output_range / input_range + output_start;

8voto

Michael Sims Points 1227

Il est GARANTI de faire correspondre N'IMPORTE QUELLE gamme à N'IMPORTE QUELLE gamme.

J'ai écrit cette méthode, qui suit précisément la formule algébrique permettant de faire passer un nombre d'un intervalle à un autre. Les calculs sont effectués avec des doubles pour maintenir la précision, et à la fin, la méthode renvoie un double avec le nombre de décimales que vous avez spécifié dans les arguments de la méthode.

Il n'est pas nécessaire de nommer la limite inférieure et la limite supérieure d'un intervalle en tant que faible o élevé parce qu'il est indifférent que l'une des extrémités soit plus basse ou plus haute que l'autre extrémité de l'une ou l'autre des plages, la méthode permettra toujours d'établir correctement la carte du nombre.

Par exemple, si vous indiquez un intervalle comme étant [-100 à 300] ou [300 à -100], cela ne fera aucune différence. Le remappage sera toujours précis.

Voici comment utiliser la méthode dans votre code :

mapOneRangeToAnother(myNumber, fromRangeA, fromRangeB, toRangeA, toRangeB, decimalPrecision)

Voici un exemple d'utilisation de la méthode :

Plage de la source : -400 à 800
Plage de destination : 10000 à 3500
Numéro à re-cartographier : 250

double sourceA = -400;
double sourceB = 800;
double destA = 10000;
double destB = 3500;
double myNum = 250;

double newNum = mapOneRangeToAnother(myNum,sourceA,sourceB,destA,destB,2);

Result: 6479.17

Et si vous avez besoin d'un entier en retour, il suffit de passer 0 décimale pour la précision et de convertir le résultat en int comme ceci :

int myResult = (int) mapOneRangeToAnother(myNumber, 500, 200, -350, -125, 0);

Vous pouvez également déclarer que la méthode renvoie un int et supprimer l'élément précision décimale puis remplacer les deux dernières lignes par :

int calcScale = (int) Math.pow(10, 0);
return (int) Math.round(finalNumber * calcScale) / calcScale;

Dans la question du PO, ils utiliseraient la fonction comme suit :

int myResult = (int) mapOneRangeToAnother(input, input_start, input_end, output_start, output_end, 0);

et voici la méthode :

public static double mapOneRangeToAnother(double sourceNumber, double fromA, double fromB, double toA, double toB, int decimalPrecision ) {
    double deltaA = fromB - fromA;
    double deltaB = toB - toA;
    double scale  = deltaB / deltaA;
    double negA   = -1 * fromA;
    double offset = (negA * scale) + toA;
    double finalNumber = (sourceNumber * scale) + offset;
    int calcScale = (int) Math.pow(10, decimalPrecision);
    return (double) Math.round(finalNumber * calcScale) / calcScale;
}

Dans mon cas d'utilisation, j'avais besoin d'estomper l'opacité d'un contrôle JavaFX, mais comme l'opacité est un nombre de 0 à 1, j'ai simplement utilisé la méthode pour remapper l'intervalle de 1 à 100 (basé sur une boucle for qui incrémentait un int de 0 à 100) à l'intervalle de 0 à 1 et cela a fonctionné parfaitement.

Je sais maintenant que j'aurais pu créer ma boucle en changeant l'incrément de 1 à quelque chose comme 0,01, comme ceci :

for(double x=0; x<=1; x+=.01 {
    //Code to change controls opacity
}
Je l'ai juste signalé pour ceux qui pourraient faire quelque chose de similaire à ce que je faisais. La méthode fonctionne parfaitement comme décrit.

-)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X