Convertir un rango de números a otro rango, manteniendo la relación

Estoy tratando de convertir un rango de números a otro, manteniendo la proporción. Las matemáticas no son mi punto fuerte.

Tengo un archivo de imagen donde los valores de los puntos pueden variar de -16000.00 a 16000.00, aunque el rango típico puede ser mucho menor. Lo que quiero hacer es comprimir estos valores en el rango entero 0-100, donde 0 es el valor del punto más pequeño y 100 es el valor del más grande. Todos los puntos intermedios deben mantener una relación relativa, aunque se pierda cierta precisión. Me gustaría hacer esto en Python, pero incluso un algoritmo general debería ser suficiente. Preferiría un algoritmo donde se pueda ajustar el rango mínimo / máximo o cualquiera de los dos (es decir, el segundo rango podría ser de -50 a 800 en lugar de 0 a 100).

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

O un poco más legible:

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

O si desea proteger para el caso donde el rango anterior es 0 ( OldMin = OldMax ):

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

Tenga en cuenta que en este caso nos vemos obligados a elegir uno de los posibles nuevos valores de rango de forma arbitraria. Según el contexto, las opciones razonables podrían ser: NewMin ( ver muestra ), NewMax o (NewMin + NewMax) / 2

Esa es una simple conversión lineal.

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

Entonces, convertir 10000 en la escala de -16000 a 16000 a una nueva escala de 0 a 100 produce:

 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 

En realidad hay algunos casos que las respuestas anteriores se romperían. Como el valor de entrada incorrecto, el rango de entrada equivocado, los rangos de entrada / salida negativos.

 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 

Hay una condición, cuando todos los valores que está verificando son los mismos, donde el código de @jerryjvl devolvería NaN.

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

No desenterré el BNF para esto, pero la documentación de Arduino tenía un gran ejemplo de la función y su desglose. Pude usar esto en Python simplemente agregando un cambio de nombre de definición a remap (el mapa de causa está incorporado) y eliminando los moldes de tipo y las llaves (es decir, solo elimine todos los ‘largos’).

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

Pitón

 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

En el listado proporcionado por PenguinTD, no entiendo por qué se invierten los rangos, funciona sin tener que revertir los rangos. La conversión de rango lineal se basa en la ecuación lineal Y=Xm+n , donde Y=Xm+n se derivan de los rangos dados. En lugar de referirse a los rangos como min y max , sería mejor referirse a ellos como 1 y 2. Por lo tanto, la fórmula sería:

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

Donde Y=y1 cuando X=x1 , e Y=y2 cuando X=x2 . x1 , x2 , y1 y y2 pueden recibir cualquier valor positive o negative . Definir la expresión en una macro la hace más útil, luego puede usarse con cualquier nombre de argumento.

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

La float flotante aseguraría la división de punto flotante en el caso de que todos los argumentos sean valores integer . Dependiendo de la aplicación, puede que no sea necesario verificar los rangos x1=x2 e y1==y2 .

Puerto PHP

Encontré la solución de PenguinTD útil, así que la porté a PHP. ¡Ayudar a sí mismo!

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

Utilicé esta solución en un problema que estaba resolviendo en js, así que pensé en compartir la traducción. Gracias por la explicación y solución.

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

Variante de C ++

Encontré la solución de PenguinTD útil, así que la porté a C ++ si alguien la necesita:

reasignación del flotador (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; } 

Aquí hay algunas funciones cortas de Python para facilitar su copia y pegado, incluida una función para escalar una lista completa.

 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] 

Que se puede utilizar como tal:

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

[0.0, 50.0, 75.0, 100.0]

En mi caso quise escalar una curva logarítmica, como así:

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

[0.0, 21.533827903669653, 34.130309724299266, 43.06765580733931, 50.0]

Propuesta abreviada / simplificada

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

Wayne

Personalmente uso la clase de ayuda que admite los generics (compatible con Swift 3)

 struct Rescale { 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) ) } } 

Este ejemplo convierte la posición actual de una canción en un rango de ángulo de 20 – 40.

  ///  /// This test converts Current songtime to an angle in a range. ///  [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); } ///  /// Gets the current value from the mixValue maxValue range. ///  /// Start of the song ///  ///  ///  /// Current time ///  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; } 

Aquí hay una versión de Javascript que devuelve una función que realiza el reescalado para rangos de origen y destino predeterminados, minimizando la cantidad de cómputo que se debe hacer cada vez.

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

Este es un ejemplo del uso de esta función para escalar 0-1 en -0x80000000, 0x7FFFFFFF

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