2009-05-30 18 views
177

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

Tengo un archivo de imagen donde los valores de los puntos pueden oscilar entre -16000.00 y 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 algo de precisión. Me gustaría hacerlo 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 (es decir, el segundo rango podría ser de -50 a 800 en lugar de 0 a 100).

+0

Gracias a los dos , Estoy dando g la respuesta a cletus porque él entró primero y un +1 a jerry por responder mi seguimiento. – SpliFF

+1

lo sentimos, lo estoy dando a Jerry porque es nuevo y necesita los puntos. – SpliFF

+1

¡Oye, eso es el envejecimiento! Heheh, j/k, sin preocupaciones. :) – cletus

Respuesta

353
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 estamos obligados a elegir arbitrariamente uno de los posibles nuevos valores de rango. Dependiendo del contexto, las opciones sensibles podrían ser: NewMin (ver la muestra), NewMax o (NewMin + NewMax)/2

+0

¿tiene oldMax que ser 16000 o puede ser el valor más alto en el antiguo conjunto de puntos (por ejemplo, 15034.00, por ejemplo) es la distinción importante? – SpliFF

+5

Puede hacer que sea lo que quiera ... tenga en cuenta que puede obtener resultados extraños si uno de los rangos es muy pequeño en relación con el otro (no estoy seguro, pero si hay más de 1000000 de diferencia de factor entre el tamaño de los rangos, asegúrese de que realmente se comporte como espera ... u obtenga información sobre la inexactitud del punto flotante) – jerryjvl

+1

Considerando la popularidad de esta respuesta, para un caso más general, debería considerar la posibilidad OldMax == OldMin, que podría dar como resultado un división por cero. – Medorator

49

Es una conversión lineal simple.

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

Así conversión de 10.000 en la escala de -16 000 a 16000 una nueva escala de 0 a 100 rendimientos:

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

Esto está mal. Debes restar Old Min de Old Value antes de la división. – SPWorley

+21

Um, soy ...... – cletus

10

hay una condición, cuando todos los valores que usted está mirando son los mismos, donde el código de @ jerryjvl volvería NaN.

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

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

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 
1

Utilicé esta solución en un problema que estaba resolviendo en js, así que pensé que compartiría la traducción. Gracias por la explicación y la 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; 
} 
+0

gracias! ¡Solución increíble y configurada como una función lista para funcionar! –

1

C++ variante

he encontrado muy útil Solución de PenguinTD, así que portado a C++, si alguien lo necesita:

flotador de reasignación (float x, flotar Omin, flotar OMAX, flotar nMin , flotar 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; } 
2

En la lista proporcionada por PenguinTD, no lo hago ONU entender por qué los rangos se invierten, funciona sin tener que invertir los rangos. La conversión de rango lineal se basa en la ecuación lineal Y=Xm+n, donde m y n se derivan de los intervalos dados. En lugar de referirse a los rangos como min y max, sería mejor referirse a ellos como 1 y 2.Por lo que la fórmula sería:

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

Dónde Y=y1 cuando X=x1, y cuando Y=y2X=x2. x1, x2, y1 & y2 se le puede dar cualquier valor de positive o negative. Definir la expresión en una macro lo 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) 

El reparto float aseguraría división de coma flotante en el caso en que todos los argumentos son integer valores. Dependiendo de la aplicación, puede que no sea necesario verificar los rangos x1=x2 y y1==y2.

+0

¡Gracias! _here es C# conversión: _ 'flotar RangeConv (float de entrada, x1 flotador, x2 flotador, y1, flotador y2) { retorno (((entrada - x1) * (y2 - y1))/(x2 - x1)) + y1; } ' – Zunair

0

Atajo/propuesta simplificada

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

Wayne

+0

¿Qué es' NewRange/OldRange' aquí? – Zunair

2

PHP Puerto

encontrado la solución de PenguinTD útiles por lo que se portado 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; 
} 
+0

Gracias, eso fue muy útil. – dearsina

1

Aquí hay algunas funciones breves de Python para copiar y pegar, incluyendo 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 pueden utilizar de este modo:

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

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

En mi caso yo quería escalar una curva logarítmica, como Entonces:

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

[0.0, 21.533827903669653, 34.130309724299266, 43,06765580733931, 50.0]

0

Yo personalmente uso la clase de ayuda que apoya a los genéricos (Swift 3 compatible)

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)) 
    } 
} 
2

yo no desenterrar el BNF para este , pero la documentación de Arduino tenía un gran ejemplo de la función y su descomposición. Pude utilizar esto en Python simplemente agregando un cambio de nombre a remapeo (porque el mapa está incorporado) y eliminando los moldes tipo y las llaves (es decir, simplemente eliminé 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; 
} 

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

0

En este ejemplo se convierte una posición actual canciones en un rango de ángulos 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; 
    } 
Cuestiones relacionadas