Convertir une plage numérique en une autre plage, en maintenant le ratio

257

J'essaie de convertir une plage de nombres en une autre, en maintenant le ratio. Les mathématiques ne sont pas mon point fort.

J'ai un fichier image où les valeurs en points peuvent varier de -16000,00 à 16000,00 bien que la plage typique puisse être beaucoup moins. Ce que je veux faire, c'est compresser ces valeurs dans la plage entière 0-100, où 0 est la valeur du plus petit point et 100 est la valeur du plus grand. Tous les points entre les deux devraient garder un rapport relatif même si une certaine précision est perdue. J'aimerais le faire en python, mais même un algorithme général devrait suffire. Je préférerais un algorithme dans lequel la plage min / max ou l'une des deux peut être ajustée (c'est-à-dire que la deuxième plage pourrait être de -50 à 800 au lieu de 0 à 100).

Joint
la source
Merci à vous deux, je donne la réponse à Cletus car il est entré en premier et un +1 à Jerry pour avoir répondu à mon suivi.
SpliFF
2
cletus désolé, je le donne à Jerry car il est nouveau et a besoin de points.
SpliFF
2
Hé c'est l'âgisme! Heheh, j / k, pas de soucis. :)
cletus
7
Comment cette question a-t-elle échappé à la brigade de fermetures de stackoverflow? ;)
thanikkal

Réponses:

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

Ou un peu plus lisible:

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

Ou si vous souhaitez vous protéger dans le cas où l'ancienne plage est 0 ( OldMin = OldMax ):

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

Notez que dans ce cas, nous sommes obligés de choisir arbitrairement l'une des nouvelles valeurs de plage possibles. Selon le contexte, des choix judicieux pourraient être: NewMin( voir exemple ), NewMaxou(NewMin + NewMax) / 2

jerryjvl
la source
oldMax doit-il être 16000 ou peut-il être la valeur la plus élevée de l'ancien ensemble de points (disons, 15034.00, par exemple) la distinction est-elle importante?
SpliFF
5
Vous pouvez en faire ce que vous voulez ... gardez à l'esprit que vous pouvez obtenir des résultats étranges si l'une des plages est très petite par rapport à l'autre (pas exactement sûr, mais s'il y a plus d'une différence de facteur de 1000000 entre la taille de les gammes, assurez-vous qu'il se comporte réellement comme vous vous y attendez ... ou découvrez l'inexactitude en virgule flottante)
jerryjvl
2
Compte tenu de la popularité de cette réponse, pour un cas plus général, vous devriez envisager la possibilité OldMax == OldMin, qui pourrait entraîner une division par zéro.
utilisateur
3
C'est génial. Y a-t-il un nom mathématique pour cette conversion?
Tarik
2
Cela s'appelle la conversion linéaire, @Tarik
Rodrigo Borba
65

C'est une simple conversion linéaire.

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

Ainsi, la conversion de 10000 sur une échelle de -16000 à 16000 vers une nouvelle échelle de 0 à 100 donne:

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
cletus
la source
2
C'est faux. Vous devez soustraire Old Min de Old Value avant la division.
SPWorley
20

En fait, il y a des cas où les réponses ci-dessus se briseraient. Tels que la valeur d'entrée incorrecte, la plage d'entrée incorrecte, les plages d'entrée / sortie négatives.

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
PenguinTD
la source
9

Il y a une condition, lorsque toutes les valeurs que vous vérifiez sont les mêmes, où le code de @ jerryjvl retournerait NaN.

if (OldMin != OldMax && NewMin != NewMax):
    return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
else:
    return (NewMax + NewMin) / 2
Guirlande en peluche
la source
5

Je n'ai pas déterré le BNF pour cela, mais la documentation Arduino avait un excellent exemple de la fonction et de sa panne. J'ai pu utiliser cela en Python en ajoutant simplement un changement de nom à remapper (car la carte est intégrée) et en supprimant les types et les accolades (c.-à-d. Supprimez simplement tous les «longs»).

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

dragon788
la source
2

Dans la liste fournie par PenguinTD, je ne comprends pas pourquoi les plages sont inversées, cela fonctionne sans avoir à inverser les plages. La conversion de plage linéaire est basée sur l'équation linéaire Y=Xm+n, où met nsont dérivées des plages données. Plutôt que de se référer aux plages comme minet max, il serait préférable de les désigner comme 1 et 2. La formule serait donc:

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

Où, Y=y1quand X=x1et Y=y2quand X=x2. x1, x2, y1Et y2peut donner une positiveou negativevaleur. La définition de l'expression dans une macro la rend plus utile, elle peut ensuite être utilisée avec n'importe quel nom d'argument.

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

Le floattranstypage garantirait une division en virgule flottante dans le cas où tous les arguments sont des integervaleurs. Selon l'application, il peut ne pas être nécessaire de vérifier les plages x1=x2et y1==y2.

Brian Plummer
la source
Merci! voici la conversion C #: float RangeConv(float input, float x1, float x2, float y1, float y2) { return (((input - x1) * (y2 - y1)) / (x2 - x1)) + y1; }
Zunair
2

Voici quelques fonctions Python courtes pour votre facilité de copier-coller, y compris une fonction pour mettre à l'échelle une liste entière.

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]

Qui peut être utilisé comme ceci:

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

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

Dans mon cas, je voulais mettre à l'échelle une courbe logarithmique, comme ceci:

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

[0,0, 21,533827903669653, 34,130309724299266, 43,06765580733931, 50,0]

Charles Clayton
la source
1

J'ai utilisé cette solution dans un problème que je résolvais en js, alors j'ai pensé partager la traduction. Merci pour l'explication et la solution.

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;
}
fonstok
la source
Merci! solution géniale et définie comme une fonction prête à l'emploi!
Fight Fire With Fire
1

Variante C ++

J'ai trouvé la solution de PenguinTD utile, donc je l'ai portée en C ++ si quelqu'un en a besoin:

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; }
user1767754
la source
1

Port PHP

La solution de PenguinTD a été utile, alors je l'ai portée sur PHP. Aide-toi!

/**
* =====================================
*              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;
}
Neil
la source
1

Voici une version Javascript qui renvoie une fonction qui effectue le redimensionnement pour des plages de source et de destination prédéterminées, minimisant la quantité de calcul qui doit être effectuée à chaque fois.

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

Voici un exemple d'utilisation de cette fonction pour mettre à l'échelle 0-1 en -0x80000000, 0x7FFFFFFF

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

Proposition raccourcie / simplifiée

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

Wayne

Wayne
la source
1
Qu'y a-t-il NewRange/OldRangeici?
Zunair
0

J'utilise personnellement la classe d'assistance qui prend en charge les génériques (compatible avec Swift 3)

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) )
    }
}
Grzegorz Krukowski
la source
0

Cet exemple convertit la position actuelle d'un morceau dans une plage d'angle de 20 à 40.

    /// <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;
    }
cavalier1210
la source
0

Comprendre la liste d'une solution de doublure

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

Version plus longue

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
zulia
la source
0

Version Java

Fonctionne toujours, peu importe ce que vous lui donnez!

J'ai laissé tout développé afin qu'il soit plus facile à suivre pour apprendre. L'arrondi à la fin est bien entendu facultatif.

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

}
Michael Sims
la source