Konvertieren Sie einen Zahlenbereich in einen anderen Bereich, wobei Sie das Verhältnis beibehalten


257

Ich versuche, einen Zahlenbereich in einen anderen umzuwandeln, wobei das Verhältnis beibehalten wird. Mathe ist nicht meine Stärke.

Ich habe eine Bilddatei, in der die Punktwerte zwischen -16000,00 und 16000,00 liegen können, obwohl der typische Bereich viel geringer sein kann. Ich möchte diese Werte in den ganzzahligen Bereich 0-100 komprimieren, wobei 0 der Wert des kleinsten Punkts und 100 der Wert des größten ist. Alle Punkte dazwischen sollten ein relatives Verhältnis beibehalten, obwohl etwas Präzision verloren geht. Ich würde dies gerne in Python tun, aber selbst ein allgemeiner Algorithmus sollte ausreichen. Ich würde einen Algorithmus bevorzugen, bei dem der Min / Max- oder einer der Bereiche angepasst werden kann (dh der zweite Bereich könnte -50 bis 800 anstelle von 0 bis 100 sein).


Ich danke Ihnen beiden, ich gebe Cletus die Antwort, weil er zuerst eingestiegen ist, und Jerry eine +1 für die Beantwortung meines Follow-up.
SpliFF

2
Tut mir leid, Cletus, ich gebe es Jerry, weil er neu ist und die Punkte braucht.
SpliFF

2
Hey, das ist Ageismus! Heheh, j / k, keine Sorge. :)
Cletus

7
Wie ist diese Frage der Stackoverflow-Frageschließer-Brigade entkommen? ;)
thanikkal

Antworten:


535
NewValue = (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin

Oder etwas lesbarer:

OldRange = (OldMax - OldMin)  
NewRange = (NewMax - NewMin)  
NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin

Oder wenn Sie für den Fall schützen möchten, dass der alte Bereich 0 ist ( OldMin = OldMax ):

OldRange = (OldMax - OldMin)
if (OldRange == 0)
    NewValue = NewMin
else
{
    NewRange = (NewMax - NewMin)  
    NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin
}

Beachten Sie, dass wir in diesem Fall gezwungen sind, einen der möglichen neuen Bereichswerte willkürlich auszuwählen. Je nach Kontext können sinnvolle Entscheidungen getroffen werden: NewMin( siehe Beispiel ) NewMaxoder(NewMin + NewMax) / 2


Muss oldMax 16000 sein oder kann es der höchste Wert in der alten Punktmenge sein (z. B. 15034.00). Ist die Unterscheidung wichtig?
SpliFF

5
Sie können alles machen, was Sie wollen ... denken Sie daran, dass Sie seltsame Ergebnisse erzielen können, wenn einer der Bereiche im Vergleich zum anderen sehr klein ist (nicht genau sicher, aber wenn es einen Unterschied von mehr als 1000000 Faktoren zwischen der Größe von gibt Stellen Sie in den Bereichen sicher, dass es sich tatsächlich so verhält, wie Sie es erwarten ... oder lernen Sie etwas über Gleitkomma-Ungenauigkeiten)
jerryjvl

2
In Anbetracht der Popularität dieser Antwort sollten Sie für einen allgemeineren Fall die Möglichkeit von OldMax == OldMin in Betracht ziehen, die zu einer Division durch Null führen könnte.
Benutzer

3
Das ist fantastisch. Gibt es einen mathematischen Namen für diese Konvertierung?
Tarik

2
Es heißt lineare Umwandlung, @Tarik
Rodrigo Borba

65

Das ist eine einfache lineare Konvertierung.

new_value = ( (old_value - old_min) / (old_max - old_min) ) * (new_max - new_min) + new_min

Die Umrechnung von 10000 auf der Skala von -16000 bis 16000 in eine neue Skala von 0 bis 100 ergibt also:

old_value = 10000
old_min = -16000
old_max = 16000
new_min = 0
new_max = 100

new_value = ( ( 10000 - -16000 ) / (16000 - -16000) ) * (100 - 0) + 0
          = 81.25

2
Das ist falsch. Sie müssen Old Min vom Old Value vor der Division subtrahieren.
SPWorley

20

Tatsächlich gibt es einige Fälle, in denen die obigen Antworten brechen würden. Wie falsch eingegebener Wert, falsch eingegebener Bereich, negative Eingabe- / Ausgabebereiche.

def remap( x, oMin, oMax, nMin, nMax ):

    #range check
    if oMin == oMax:
        print "Warning: Zero input range"
        return None

    if nMin == nMax:
        print "Warning: Zero output range"
        return None

    #check reversed input range
    reverseInput = False
    oldMin = min( oMin, oMax )
    oldMax = max( oMin, oMax )
    if not oldMin == oMin:
        reverseInput = True

    #check reversed output range
    reverseOutput = False   
    newMin = min( nMin, nMax )
    newMax = max( nMin, nMax )
    if not newMin == nMin :
        reverseOutput = True

    portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin)
    if reverseInput:
        portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin)

    result = portion + newMin
    if reverseOutput:
        result = newMax - portion

    return result

#test cases
print remap( 25.0, 0.0, 100.0, 1.0, -1.0 ), "==", 0.5
print remap( 25.0, 100.0, -100.0, -1.0, 1.0 ), "==", -0.25
print remap( -125.0, -100.0, -200.0, 1.0, -1.0 ), "==", 0.5
print remap( -125.0, -200.0, -100.0, -1.0, 1.0 ), "==", 0.5
#even when value is out of bound
print remap( -20.0, 0.0, 100.0, 0.0, 1.0 ), "==", -0.2

9

Es gibt eine Bedingung, bei der alle von Ihnen überprüften Werte gleich sind und der Code von @ jerryjvl NaN zurückgibt.

if (OldMin != OldMax && NewMin != NewMax):
    return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
else:
    return (NewMax + NewMin) / 2

5

Ich habe die BNF dafür nicht ausgegraben , aber die Arduino-Dokumentation hatte ein großartiges Beispiel für die Funktion und ihre Aufteilung. Ich konnte dies in Python verwenden, indem ich einfach eine Umbenennung für die Neuzuordnung hinzufügte (weil die Karte integriert ist) und die Typumwandlungen und geschweiften Klammern entfernte (dh einfach alle 'Longs' entfernte).

Original

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

Python

def remap(x, in_min, in_max, out_min, out_max):
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min

https://www.arduino.cc/en/reference/map


2

In der von PenguinTD bereitgestellten Liste verstehe ich nicht, warum die Bereiche umgekehrt werden. Es funktioniert, ohne dass die Bereiche umgekehrt werden müssen. Die lineare Bereichsumwandlung basiert auf der linearen Gleichung Y=Xm+n, wobei mund naus den angegebenen Bereichen abgeleitet werden. Anstatt die Bereiche als minund zu bezeichnen max, ist es besser, sie als 1 und 2 zu bezeichnen. Die Formel wäre also:

Y = (((X - x1) * (y2 - y1)) / (x2 - x1)) + y1

Wo Y=y1wann X=x1und Y=y2wann X=x2. x1, x2, y1& y2Kann zu jedem gegebenen werden positiveoder negativeWert. Das Definieren des Ausdrucks in einem Makro macht ihn nützlicher und kann dann mit beliebigen Argumentnamen verwendet werden.

#define RangeConv(X, x1, x2, y1, y2) (((float)((X - x1) * (y2 - y1)) / (x2 - x1)) + y1)

Die floatUmwandlung würde eine Gleitkommadivision sicherstellen, wenn alle Argumente integerWerte sind. Je nach Anwendung ist es möglicherweise nicht erforderlich, die Bereiche x1=x2und zu überprüfen y1==y2.


Vielen Dank! Hier ist C # Konvertierung: float RangeConv(float input, float x1, float x2, float y1, float y2) { return (((input - x1) * (y2 - y1)) / (x2 - x1)) + y1; }
Zunair

2

Hier sind einige kurze Python-Funktionen zum einfachen Kopieren und Einfügen, einschließlich einer Funktion zum Skalieren einer gesamten Liste.

def scale_number(unscaled, to_min, to_max, from_min, from_max):
    return (to_max-to_min)*(unscaled-from_min)/(from_max-from_min)+to_min

def scale_list(l, to_min, to_max):
    return [scale_number(i, to_min, to_max, min(l), max(l)) for i in l]

Welches kann so verwendet werden:

scale_list([1,3,4,5], 0, 100)

[0,0, 50,0, 75,0, 100,0]

In meinem Fall wollte ich eine logarithmische Kurve wie folgt skalieren:

scale_list([math.log(i+1) for i in range(5)], 0, 50)

[0.0, 21.533827903669653, 34.130309724299266, 43.06765580733931, 50.0]


1

Ich habe diese Lösung in einem Problem verwendet, das ich in js gelöst habe, also dachte ich, ich würde die Übersetzung teilen. Danke für die Erklärung und Lösung.

function remap( x, oMin, oMax, nMin, nMax ){
//range check
if (oMin == oMax){
    console.log("Warning: Zero input range");
    return None;
};

if (nMin == nMax){
    console.log("Warning: Zero output range");
    return None
}

//check reversed input range
var reverseInput = false;
oldMin = Math.min( oMin, oMax );
oldMax = Math.max( oMin, oMax );
if (oldMin != oMin){
    reverseInput = true;
}

//check reversed output range
var reverseOutput = false;  
newMin = Math.min( nMin, nMax )
newMax = Math.max( nMin, nMax )
if (newMin != nMin){
    reverseOutput = true;
};

var portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin)
if (reverseInput){
    portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin);
};

var result = portion + newMin
if (reverseOutput){
    result = newMax - portion;
}

return result;
}

Danke! tolle lösung und als funktionsbereit eingestellt!
Bekämpfe Feuer mit Feuer

1

C ++ Variante

Ich fand die Lösung von PenguinTD nützlich, also habe ich sie nach C ++ portiert, wenn jemand sie benötigt:

float remap (float x, float oMin, float oMax, float nMin, float nMax) {

//range check
if( oMin == oMax) {
    //std::cout<< "Warning: Zero input range";
    return -1;    }

if( nMin == nMax){
    //std::cout<<"Warning: Zero output range";
    return -1;        }

//check reversed input range
bool reverseInput = false;
float oldMin = min( oMin, oMax );
float oldMax = max( oMin, oMax );
if (oldMin == oMin)
    reverseInput = true;

//check reversed output range
bool reverseOutput = false;  
float newMin = min( nMin, nMax );
float newMax = max( nMin, nMax );
if (newMin == nMin)
    reverseOutput = true;

float portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin);
if (reverseInput)
    portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin);

float result = portion + newMin;
if (reverseOutput)
    result = newMax - portion;

return result; }

1

PHP-Port

Ich fand die Lösung von PenguinTD hilfreich, also habe ich sie auf PHP portiert. Bedienen Sie sich!

/**
* =====================================
*              Remap Range            
* =====================================
* - Convert one range to another. (including value)
*
* @param    int $intValue   The value in the old range you wish to convert
* @param    int $oMin       The minimum of the old range
* @param    int $oMax       The maximum of the old range
* @param    int $nMin       The minimum of the new range
* @param    int $nMax       The maximum of the new range
*
* @return   float $fResult  The old value converted to the new range
*/
function remapRange($intValue, $oMin, $oMax, $nMin, $nMax) {
    // Range check
    if ($oMin == $oMax) {
        echo 'Warning: Zero input range';
        return false;
    }

    if ($nMin == $nMax) {
        echo 'Warning: Zero output range';
        return false;
    }

    // Check reversed input range
    $bReverseInput = false;
    $intOldMin = min($oMin, $oMax);
    $intOldMax = max($oMin, $oMax);
    if ($intOldMin != $oMin) {
        $bReverseInput = true;
    }

    // Check reversed output range
    $bReverseOutput = false;
    $intNewMin = min($nMin, $nMax);
    $intNewMax = max($nMin, $nMax);
    if ($intNewMin != $nMin) {
        $bReverseOutput = true;
    }

    $fRatio = ($intValue - $intOldMin) * ($intNewMax - $intNewMin) / ($intOldMax - $intOldMin);
    if ($bReverseInput) {
        $fRatio = ($intOldMax - $intValue) * ($intNewMax - $intNewMin) / ($intOldMax - $intOldMin);
    }

    $fResult = $fRatio + $intNewMin;
    if ($bReverseOutput) {
        $fResult = $intNewMax - $fRatio;
    }

    return $fResult;
}

1

Hier ist eine Javascript-Version, die eine Funktion zurückgibt, die die Neuskalierung für vorgegebene Quell- und Zielbereiche durchführt und so den Rechenaufwand minimiert, der jedes Mal durchgeführt werden muss.

// This function returns a function bound to the 
// min/max source & target ranges given.
// oMin, oMax = source
// nMin, nMax = dest.
function makeRangeMapper(oMin, oMax, nMin, nMax ){
    //range check
    if (oMin == oMax){
        console.log("Warning: Zero input range");
        return undefined;
    };

    if (nMin == nMax){
        console.log("Warning: Zero output range");
        return undefined
    }

    //check reversed input range
    var reverseInput = false;
    let oldMin = Math.min( oMin, oMax );
    let oldMax = Math.max( oMin, oMax );
    if (oldMin != oMin){
        reverseInput = true;
    }

    //check reversed output range
    var reverseOutput = false;  
    let newMin = Math.min( nMin, nMax )
    let newMax = Math.max( nMin, nMax )
    if (newMin != nMin){
        reverseOutput = true;
    }

    // Hot-rod the most common case.
    if (!reverseInput && !reverseOutput) {
        let dNew = newMax-newMin;
        let dOld = oldMax-oldMin;
        return (x)=>{
            return ((x-oldMin)* dNew / dOld) + newMin;
        }
    }

    return (x)=>{
        let portion;
        if (reverseInput){
            portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin);
        } else {
            portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin)
        }
        let result;
        if (reverseOutput){
            result = newMax - portion;
        } else {
            result = portion + newMin;
        }

        return result;
    }   
}

Hier ist ein Beispiel für die Verwendung dieser Funktion zum Skalieren von 0-1 in -0x80000000, 0x7FFFFFFF

let normTo32Fn = makeRangeMapper(0, 1, -0x80000000, 0x7FFFFFFF);
let fs = normTo32Fn(0.5);
let fs2 = normTo32Fn(0);

0

Abkürzung / vereinfachter Vorschlag

 NewRange/OldRange = Handy multiplicand or HM
 Convert OldValue in OldRange to NewValue in NewRange = 
 (OldValue - OldMin x HM) + NewMin

Wayne


1
Was ist NewRange/OldRangehier
Zunair

0

Ich persönlich benutze die Helferklasse, die Generika unterstützt (Swift 3 kompatibel).

struct Rescale<Type : BinaryFloatingPoint> {
    typealias RescaleDomain = (lowerBound: Type, upperBound: Type)

    var fromDomain: RescaleDomain
    var toDomain: RescaleDomain

    init(from: RescaleDomain, to: RescaleDomain) {
        self.fromDomain = from
        self.toDomain = to
    }

    func interpolate(_ x: Type ) -> Type {
        return self.toDomain.lowerBound * (1 - x) + self.toDomain.upperBound * x;
    }

    func uninterpolate(_ x: Type) -> Type {
        let b = (self.fromDomain.upperBound - self.fromDomain.lowerBound) != 0 ? self.fromDomain.upperBound - self.fromDomain.lowerBound : 1 / self.fromDomain.upperBound;
        return (x - self.fromDomain.lowerBound) / b
    }

    func rescale(_ x: Type )  -> Type {
        return interpolate( uninterpolate(x) )
    }
}

0

In diesem Beispiel wird die aktuelle Position eines Songs in einen Winkelbereich von 20 bis 40 konvertiert.

    /// <summary>
    /// This test converts Current songtime to an angle in a range. 
    /// </summary>
    [Fact]
    public void ConvertRangeTests()
    {            
       //Convert a songs time to an angle of a range 20 - 40
        var result = ConvertAndGetCurrentValueOfRange(
            TimeSpan.Zero, TimeSpan.FromMinutes(5.4),
            20, 40, 
            2.7
            );

        Assert.True(result == 30);
    }

    /// <summary>
    /// Gets the current value from the mixValue maxValue range.        
    /// </summary>
    /// <param name="startTime">Start of the song</param>
    /// <param name="duration"></param>
    /// <param name="minValue"></param>
    /// <param name="maxValue"></param>
    /// <param name="value">Current time</param>
    /// <returns></returns>
    public double ConvertAndGetCurrentValueOfRange(
                TimeSpan startTime,
                TimeSpan duration,
                double minValue,
                double maxValue,
                double value)
    {
        var timeRange = duration - startTime;
        var newRange = maxValue - minValue;
        var ratio = newRange / timeRange.TotalMinutes;
        var newValue = value * ratio;
        var currentValue= newValue + minValue;
        return currentValue;
    }

0

Listenverständnis Einzeiler-Lösung

color_array_new = [int((((x - min(node_sizes)) * 99) / (max(node_sizes) - min(node_sizes))) + 1) for x in node_sizes]

Längere Version

def colour_specter(waste_amount):
color_array = []
OldRange = max(waste_amount) - min(waste_amount)
NewRange = 99
for number_value in waste_amount:
    NewValue = int((((number_value - min(waste_amount)) * NewRange) / OldRange) + 1)
    color_array.append(NewValue)
print(color_array)
return color_array

0

Java-Version

Funktioniert immer, egal was Sie füttern!

Ich habe alles erweitert gelassen, damit es einfacher ist, dem Lernen zu folgen. Das Runden am Ende ist natürlich optional.

    private long remap(long p, long Amin, long Amax, long Bmin, long Bmax ) {

    double deltaA = Amax - Amin;
    double deltaB = Bmax - Bmin;
    double scale  = deltaB / deltaA;
    double negA   = -1 * Amin;
    double offset = (negA * scale) + Bmin;
    double q      = (p * scale) + offset;
    return Math.round(q);

}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.