Vergessen wir die Mathematik und versuchen wir, dies intuitiv zu lösen.
Wenn wir Eingabenummern im Bereich [ 0
, x
] dem Ausgabebereich [ 0
, y
] zuordnen möchten , müssen wir zunächst nur um einen geeigneten Betrag skalieren. 0 geht zu 0, x
geht zu y
und eine Zahl t
geht zu (y/x)*t
.
Reduzieren wir Ihr Problem auf das oben beschriebene einfachere Problem.
Ein Eingabebereich von [ input_start
, input_end
] hat input_end - input_start + 1
Zahlen. Es entspricht also einem Bereich von [ 0
, r
], wo r = input_end - input_start
.
In ähnlicher Weise entspricht der Ausgabebereich [ 0
, R
], wobei R = output_end - output_start
.
Eine Eingabe von input
entspricht x = input - input_start
. Dies wird ab dem ersten Absatz übersetzt in y = (R/r)*x
. Anschließend können wir den y
Wert durch Hinzufügen von output_start
: wieder in den ursprünglichen Ausgabebereich übersetzen output = output_start + y
.
Dies gibt uns:
output = output_start + ((output_end - output_start) / (input_end - input_start)) * (input - input_start)
Oder anders:
slope = (output_end - output_start) / (input_end - input_start)
output = output_start + slope * (input - input_start)
Da dies C ist und die Division in C abgeschnitten ist, sollten Sie versuchen, eine genauere Antwort zu erhalten, indem Sie die Dinge im Gleitkomma berechnen:
double slope = 1.0 * (output_end - output_start) / (input_end - input_start)
output = output_start + slope * (input - input_start)
Wenn Sie noch korrekter sein möchten, würden Sie im letzten Schritt eine Rundung anstelle einer Kürzung durchführen. Sie können dies tun, indem Sie eine einfache round
Funktion schreiben :
#include <math.h>
double round(double d)
{
return floor(d + 0.5);
}
Dann:
output = output_start + round(slope * (input - input_start))
output_start + ((output_end - output_start) * (input - input_start)) / (input_end - input_start)
. Siehe diese Gleichung(a * b) / c
ist das gleiche wie(a / c) * b
- zumindest mathematisch. Um die Gleitkomma-Genauigkeit zu gewährleisten und sicherzustellen, dass Sie keine Überläufe haben, müssen Sie die Reihenfolge der Operationen sehr genau beachten und können abhängig von den Eingabewerten sogar eine andere Reihenfolge der Operationen haben.Arduino hat diese Karte integriert .
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; }
quelle
map
Funktion ist gebrochen. Es wurde von einer Gleitkommafunktion konvertiert, indem einfach alle Argumente in int geändert wurden, und es hat eine fehlerhafte Verteilung.Die Formel lautet
Ich werde diesen Beitrag hier anschließen: https://betterexplained.com/articles/rethinking-arithmetic-a-visual-guide/, da es mir sehr geholfen hat, dies intuitiv zu finden. Sobald Sie verstanden haben, was der Beitrag sagt, ist es trivial, diese Formeln selbst zu erstellen. Beachten Sie, dass ich auch mit solchen Fragen zu kämpfen hatte. (Ich habe keine Zugehörigkeit - fand es nur sehr nützlich)
Angenommen, Sie haben eine Reichweite
[input_start..input_end]
. Beginnen wir damit, sie so zu normalisieren, dass 0 istinput_start
und 1 istinput_end
. Dies ist eine einfache Technik, um das Problem zu vereinfachen.wie machen wir das? Wir müssten alles, was noch übrig ist, um den Betrag input_start verschieben, so dass, wenn Eingabe x zufällig ist
input_start
, sie Null ergeben sollte.Nehmen wir also an, es
f(x)
ist die Funktion, die die Konvertierung durchführt.Lass es uns versuchen:
f(input_start) = input_start - input_start = 0
arbeitet für
input_start
.Zu diesem Zeitpunkt funktioniert es noch
input_end
nicht, da wir es nicht skaliert haben.Skalieren wir es einfach um die Länge des Bereichs, dann wird der größte Wert (input_end) einem zugeordnet.
ok, lass es uns versuchen mit
input_end
.f
(input_end) = (input_end - input_start) / (input_end - input_start) = 1
genial, 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:
Jetzt sind wir fast fertig. Wir müssen es nur noch nach rechts verschieben, damit 0 von output_start ausgeht.
Lassen Sie es uns kurz versuchen.
Sie sehen, dass der erste Teil der Gleichung so ziemlich mit Null multipliziert wird, wodurch alles aufgehoben wird und Sie erhalten
Lass es uns auch versuchen
input_end
.was wiederum enden wird als:
Wie Sie sehen können, scheint es jetzt korrekt zugeordnet zu sein.
quelle
Der entscheidende Punkt hierbei ist, die Ganzzahldivision (einschließlich Rundung) an der richtigen Stelle durchzuführen. Keine der bisherigen Antworten stimmte mit den Klammern überein. 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;
quelle
Dabei wird proportional "wie weit" der Eingangsbereich der Eingabe ermittelt. Dieser Anteil wird dann auf die Größe des Ausgabebereichs angewendet, um in absoluten Zahlen herauszufinden, wie weit der Ausgabebereich im Ausgabebereich liegen sollte. Anschließend wird der Anfang des Ausgabebereichs hinzugefügt, um die tatsächliche Ausgangsnummer zu erhalten.
quelle
500
, mit Ausnahme der Eingabe254
, die ergeben wird5500
.Dies ist GARANTIERT, um JEDEN Bereich JEDEM Bereich zuzuordnen
Ich habe diese Methode geschrieben, die genau der algebraischen Formel für die Zuordnung eines Zahlenbereichs zu einem anderen folgt. Die Berechnungen werden mit Doubles durchgeführt, um die Genauigkeit zu gewährleisten. Am Ende wird ein Double mit den in den Methodenargumenten angegebenen Dezimalstellen zurückgegeben.
Die Funktion funktioniert folgendermaßen ... Beachten Sie, dass ich die Enden der Bereiche A und B beschriftet habe - das liegt daran, dass es nicht erforderlich ist, dass Ihre Bereiche niedrig beginnen und hoch enden oder so etwas ... Sie können Ihren ersten Bereich 300 erstellen bis -7000 und Ihr zweiter Bereich -3500 bis 5500 ... macht keinen Unterschied, welche Zahlen Sie für Ihre Bereiche ausgewählt haben, es werden sie korrekt zugeordnet.
Und wenn Sie eine Ganzzahl zurück benötigen, sagen Sie ihr einfach, dass sie das Ergebnis mit einer Dezimalstelle von 1 zurückgeben und das Ergebnis wie folgt in int umwandeln soll:
int myResult = (int) mapOneRangeToAnother(myNumber, 500, 200, -350, -125, 1);
In der ursprünglichen Frage des OP würden sie die folgende Funktion verwenden:
int myResult = (int) mapOneRangeToAnother(input, input_start, input_end, output_start, output_end, 1);
und hier ist 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, habe ich einen Zähler eingerichtet, der von 0 bis 100 zählt, und dann den Bereich 0 - 100 bis 0 zugeordnet - 1 und es werden alle Brüche zwischen 0 und 1 ermittelt, wenn die Zahlen 0 bis 100 eingegeben werden. Dies ist eine sauberere Methode, da der Hauptcode mit einer solchen Funktion besser aussieht als mit der tatsächlichen Berechnung on the fly.
Ihr Kilometerstand kann variieren ... viel Spaß! :-)
quelle