Abbildung eines numerischen Bereichs auf einen anderen

Lesezeit: 9 Minuten

Joes Benutzeravatar
Jo

Mathe war nie meine Stärke in der Schule 🙁

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;

Wie kann ich den Eingabewert in den entsprechenden Ausgabewert dieses Bereichs umwandeln?

Beispielsweise würde ein Eingangswert von „0“ einem Ausgangswert von „500“ entsprechen, ein Eingangswert von „254“ würde einem Ausgangswert von „5500“ entsprechen. Ich kann nicht herausfinden, wie ein Ausgabewert berechnet wird, wenn ein Eingabewert beispielsweise 50 oder 101 beträgt.

Ich bin sicher, es ist einfach, ich kann gerade nicht denken 🙂

Bearbeiten: Ich brauche nur ganze Zahlen, keine Brüche oder so etwas.

Vergessen wir die Mathematik und versuchen, dies intuitiv zu lösen.

Erstens, wenn wir Eingabenummern im Bereich abbilden möchten [0, x] zum Ausgangsbereich [0, y], müssen wir nur um einen geeigneten Betrag skalieren. 0 geht auf 0, x geht zu yund eine Zahl t werde gehen (y/x)*t.

Reduzieren wir also Ihr Problem auf das obige einfachere Problem.

Ein Eingangsbereich von [input_start, input_end] hat input_end - input_start + 1 Zahlen. Es ist also gleichbedeutend mit einer Reihe von [0, r]wo r = input_end - input_start.

Ebenso ist der Ausgangsbereich äquivalent zu [0, R]wo R = output_end - output_start.

Eine Eingabe von input ist äquivalent zu x = input - input_start. Dies wird aus dem ersten Absatz übersetzt y = (R/r)*x. Dann können wir die übersetzen y Wert durch Addition auf den ursprünglichen Ausgangsbereich zurück output_start: output = output_start + y.

Das gibt uns:

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

Oder anders:

/* 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)

Da dies nun C ist und die Division in C abschneidet, sollten Sie versuchen, eine genauere Antwort zu erhalten, indem Sie die Dinge in Gleitkommazahlen berechnen:

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

Wenn Sie noch genauer sein wollten, würden Sie im letzten Schritt eine Rundung statt einer Kürzung vornehmen. Sie können dies tun, indem Sie eine einfache schreiben round Funktion:

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

Dann:

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

  • Ich habe diese Funktion im Laufe der Jahrzehnte mindestens ein paar Mal geschrieben, und doch ist diese Erklärung besser, als ich mir selbst geben könnte. +1 zum Herausziehen der Teilung – war nützlich für 6,5 Millionen Berechnungen pro Lauf.

    – delrocco

    24. Oktober 2018 um 19:30 Uhr

  • Dies ist eine der besten Antworten, die ich auf dieser Website gesehen habe. Vielen Dank!

    – foobarbaz

    16. Februar 2019 um 19:53 Uhr

  • Was für eine schöne Herangehensweise an das Problem! Hervorragend beantwortet!

    – Adele Goldberg

    7. Oktober 2019 um 12:43 Uhr

  • Ich habe festgestellt, dass die Reihenfolge der Operationen hier falsch ist. Es sollte sein output_start + ((output_end - output_start) * (input - input_start)) / (input_end - input_start) . Sehen diese Gleichung

    – StapelaufStapel

    27. August 2020 um 23:56 Uhr


  • Dies setzt eine lineare Abbildung voraus. Wenn Sie sinusförmig, parabolisch, exponentiell, logarithmisch, invers parabolisch, elliptisch oder kreisförmig wollen, habe ich eine entwickelt Desmos-Diagramm mit diesen Formeln.

    – rg

    8. Juni 2021 um 3:11 Uhr

Arduino hat dies als eingebaut Karte.

Beispiel:

/* 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);
}

Es hat auch die Implementierung auf dieser Seite:

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

  • FYI, ihre map Funktion ist kaputt. Es wurde von einer Fließkommafunktion konvertiert, indem einfach alle Argumente in int geändert wurden, und es hat eine fehlerhafte Verteilung.

    – Dreck

    22. Januar 2014 um 20:20 Uhr


  • @Mud Trotzdem ist es nützlich, wenn wir alles wieder auf Floats umstellen.

    – Kotauskas

    27. April 2020 um 11:33 Uhr

die formel ist

f(x) = (x – Eingabe_Anfang) / (Eingabe_Ende – Eingabe_Anfang) * (Ausgabe_Ende – Ausgabe_Anfang) + Ausgabe_Anfang

Ich schließe diesen Beitrag hier an: https://betterexplained.com/articles/retthinking-arithmetic-a-visual-guide/ da es mir sehr geholfen hat, intuitiv darauf zu kommen. Sobald Sie verstehen, was der Post sagt, ist es trivial, diese Formeln selbst zu finden. Beachten Sie, dass ich früher auch mit solchen Fragen zu kämpfen hatte. (Ich habe keine Zugehörigkeiten – fand es nur sehr nützlich)

Sagen Sie, Sie haben Reichweite [input_start..input_end]beginnen wir damit, es so zu normalisieren, dass 0 ist input_startund 1 ist input_end. Dies ist eine einfache Technik, um das Problem zu vereinfachen.

wie machen wir das? Wir müssen, wir müssten alles, was übrig bleibt, um input_start Betrag verschieben, so dass, wenn Eingang x zufällig ist input_startes sollte Null geben.

also sagen wir mal f(x) ist die Funktion, die die Konvertierung durchführt.

f(x) = x - input_start

Lass es uns versuchen:

f(input_start) = input_start - input_start = 0

funktioniert für input_start.

An dieser Stelle funktioniert es nicht input_end noch, da wir es nicht skaliert haben.

Lassen Sie es uns einfach um die Länge des Bereichs verkleinern, dann haben wir den größten Wert (input_end) auf eins abgebildet.

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

ok, versuchen wir es mal mit input_end.

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

super, scheint zu funktionieren.

Okay, im nächsten Schritt skalieren wir es tatsächlich auf den Ausgabebereich. Es ist so trivial wie nur das Multiplizieren mit der tatsächlichen Länge des Ausgabebereichs als solches:

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

jetzt sind wir eigentlich fast fertig, wir müssen es nur noch nach rechts verschieben, damit 0 bei output_start beginnt.

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

Probieren wir es kurz aus.

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

Sie sehen, dass der erste Teil der Gleichung ziemlich genau mit Null multipliziert wird, wodurch alles aufgehoben wird, was Sie ergibt

f(input_start) = output_start

Lass es uns versuchen input_end auch.

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

was wiederum endet als:

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

Wie Sie sehen können, scheint es jetzt korrekt zugeordnet zu sein.

  • Derjenige, der wirklich generisch arbeitet

    – Gilian

    11. Juli um 14:59 Uhr

Entscheidend ist hier, die ganzzahlige Division (inklusive Rundung) an der richtigen Stelle durchzuführen. Keine der bisherigen Antworten hat die Klammern richtig gemacht. Hier ist der richtige Weg:

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

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

Benutzeravatar von Michael Sims
Michael Sims

Dies ist GARANTIERT, um JEDEN Bereich auf JEDEN Bereich abzubilden

Ich habe diese Methode geschrieben, die genau der algebraischen Formel folgt, um einen Bereich von Zahlen auf einen anderen abzubilden. Die Berechnungen werden mit Doubles durchgeführt, um die Genauigkeit beizubehalten, und am Ende wird ein Double mit beliebig vielen Dezimalstellen zurückgegeben, die Sie in den Methodenargumenten angeben.

Die Funktion funktioniert so … Beachten Sie, dass ich die Enden der Bereiche A und B markiert habe – das liegt daran, dass es nicht notwendig ist, dass Ihre Bereiche niedrig beginnen und hoch enden oder so etwas … Sie können Ihren ersten Bereich 300 machen bis -7000 und Ihr zweiter Bereich -3500 bis 5500 … macht keinen Unterschied, welche Zahlen Sie für Ihre Bereiche gewählt haben, es wird sie korrekt abbilden.

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

Hier ist ein kurzes Beispiel für die Verwendung der Funktion:

Quellbereich: -400 bis 800
Zielbereich: 10000 bis 3500
Anzahl neu zuzuordnen: 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

Und wenn Sie eine Ganzzahl zurück benötigen, sagen Sie ihr einfach, dass sie das Ergebnis mit 0 Dezimalstellen zurückgeben und das Ergebnis wie folgt in int umwandeln soll:

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

Oder Sie könnten die Funktion so deklarieren, dass sie ein int zurückgibt, und dann einfach die letzte Zeile von return (double) in return (int) ändern.

In der OP-Frage würden sie die Funktion wie folgt verwenden:

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

und hier die funktion:

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

Ich habe diese Funktion sogar in einem Zähler verwendet, um etwas auf dem Bildschirm auszublenden, aber da die Deckkraft ein Wert zwischen 0 und 1 ist, richte ich einen Zähler ein, der von 0 bis 100 zählt, und bilde dann den Bereich 0 – 100 auf 0 ab – 1 und es erhält alle Brüche zwischen 0 und 1, basierend auf der Eingabe der Zahlen 0 bis 100 … es ist eine sauberere Methode, da der Hauptcode mit einer Funktion wie dieser besser aussieht, als die Mathematik tatsächlich durchzuführen im Flug.

Ihr Kilometerstand kann variieren … viel Spaß! 🙂

  • Tolle Funktion. Dazu habe ich eine schnelle Begrenzungslogik für sourceNumbers hinzugefügt, die möglicherweise außerhalb des gewünschten „from“-Bereichs liegen und hervorragend funktionieren.

    – Jordan Grant

    7. Februar um 9:53

  • @JordanGrant – Es ist sinnvoll, diese Prüfung hinzuzufügen, insbesondere wenn die Eingabenummer zur Laufzeit unbekannt ist (dh eine vom Benutzer eingegebene Nummer). Schön, dass Sie das nützlich fanden. 🙂

    – Michael Sims

    8. Februar um 20:42 Uhr

Benutzeravatar von QuantumMechanic
Quantenmechanik

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

Dadurch wird proportional ermittelt, “wie weit in” dem Eingangsbereich die Eingabe liegt. Dieses Verhältnis wird dann auf die Größe des Ausgabebereichs angewendet, um absolut zu ermitteln, wie weit in den Ausgabebereich hinein die Ausgabe liegen sollte. Dann fügt es den Anfang des Ausgangsbereichs hinzu, um die tatsächliche Ausgangsnummer zu erhalten.

  • Tolle Funktion. Dazu habe ich eine schnelle Begrenzungslogik für sourceNumbers hinzugefügt, die möglicherweise außerhalb des gewünschten „from“-Bereichs liegen und hervorragend funktionieren.

    – Jordan Grant

    7. Februar um 9:53

  • @JordanGrant – Es ist sinnvoll, diese Prüfung hinzuzufügen, insbesondere wenn die Eingabenummer zur Laufzeit unbekannt ist (dh eine vom Benutzer eingegebene Nummer). Schön, dass Sie das nützlich fanden. 🙂

    – Michael Sims

    8. Februar um 20:42 Uhr

1422590cookie-checkAbbildung eines numerischen Bereichs auf einen anderen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy