Converter um intervalo numérico em outro intervalo, mantendo a proporção


257

Estou tentando converter um intervalo de números para outro, mantendo a proporção. Matemática não é meu ponto forte.

Eu tenho um arquivo de imagem em que os valores dos pontos podem variar de -16000,00 a 16000,00, embora o intervalo típico possa ser muito menor. O que eu quero fazer é compactar esses valores no intervalo inteiro de 0 a 100, onde 0 é o valor do menor ponto e 100 é o valor do maior. Todos os pontos intermediários devem manter uma proporção relativa, embora alguma precisão esteja sendo perdida. Eu gostaria de fazer isso em python, mas mesmo um algoritmo geral deve ser suficiente. Eu preferiria um algoritmo em que o intervalo mínimo / máximo ou qualquer um dos intervalos possa ser ajustado (ou seja, o segundo intervalo poderia ser de -50 a 800 em vez de 0 a 100).


Obrigado a ambos, estou dando a resposta ao cletus, porque ele entrou primeiro e +1 para Jerry por responder à minha sequência.
SpliFF

2
Desculpe, estou dando a Jerry porque ele é novo e precisa dos pontos.
SpliFF

2
Ei, isso é envelhecimento! Heheh, j / k, não se preocupe. :)
cletus 30/05

7
Como essa pergunta escapou da brigada de fechamento de questão stackoverflow? ;)
thanikkal 15/10

Respostas:


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

Ou um pouco mais legível:

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

Ou se você deseja proteger para o caso em que o intervalo antigo é 0 ( OldMin = OldMax ):

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

Observe que, nesse caso, somos forçados a escolher um dos possíveis novos valores de intervalo arbitrariamente. Dependendo do contexto, as escolhas sensatas podem ser: NewMin( ver exemplo ) NewMaxou(NewMin + NewMax) / 2


oldMax precisa ser 16000 ou pode ser o valor mais alto no conjunto de pontos antigo (por exemplo, 15034,00, por exemplo) a distinção é importante?
SpliFF

5
Você pode fazer o que quiser ... lembre-se de que poderá obter resultados estranhos se um dos intervalos for muito pequeno em relação ao outro (não exatamente, mas se houver mais de 1000000 de diferença de fator entre o tamanho de os intervalos, certifique-se de que ele realmente se comporta como você espera ... ou aprender sobre flutuante imprecisão ponto)
jerryjvl

2
Considerando a popularidade desta resposta, para um caso mais geral, você deve considerar a possibilidade OldMax == OldMin, que pode resultar em uma divisão por zero.
usuário

3
Isso é incrível. Existe um nome matemático para esta conversão?
Tarik

2
É chamado de conversão linear, @Tarik
Rodrigo Borba

65

Essa é uma conversão linear simples.

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

Assim, converter 10000 na escala de -16000 a 16000 em uma nova escala de 0 a 100 produz:

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
Isto está errado. Você precisa subtrair Old Min de Old Value antes da divisão.
SPWorley

20

Na verdade, existem alguns casos em que as respostas acima quebrariam. Tais como valor de entrada incorreto, faixa de entrada incorreta, faixas de entrada / saída negativas.

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

Existe uma condição, quando todos os valores que você está verificando são os mesmos, em que o código do @ jerryjvl retornaria NaN.

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

5

Eu não desenterrei o BNF para isso, mas a documentação do Arduino tinha um ótimo exemplo da função e sua falha. Eu era capaz de usar isso no Python simplesmente adicionando uma renomeação de definição para remapear (o mapa de causas é incorporado) e removendo os tipos de conversão e chaves (ou seja, basta remover todos os 'long's').

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ão

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

Na listagem fornecida pelo PenguinTD, não entendo por que os intervalos são invertidos, ele funciona sem ter que reverter os intervalos. A conversão do intervalo linear é baseada na equação linear Y=Xm+n, onde me nsão derivados dos intervalos fornecidos. Em vez de se referir aos intervalos como mine max, seria melhor referir-se a eles como 1 e 2. Portanto, a fórmula seria:

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

Onde Y=y1quando X=x1e Y=y2quando X=x2. x1, x2, y1E y2pode ser dada qualquer positiveou negativevalor. Definir a expressão em uma macro a torna mais útil, podendo ser usada com qualquer nome de argumento.

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

A floatconversão garantiria a divisão de ponto flutuante no caso em que todos os argumentos sejam integervalores. Dependendo da aplicação, pode não ser necessário verificar os intervalos x1=x2e y1==y2.


Obrigado! aqui está a conversão de C #: float RangeConv(float input, float x1, float x2, float y1, float y2) { return (((input - x1) * (y2 - y1)) / (x2 - x1)) + y1; }
Zunair 15/07/16

2

Aqui estão algumas funções curtas do Python para facilitar a cópia e colar, incluindo uma função para dimensionar uma lista inteira.

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 pode ser usado assim:

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

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

No meu caso, eu queria escalar uma curva logarítmica, assim:

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

[0.0, 21.533827903669653, 34.130309724299266, 43,06765580733931, 50,0]


1

Eu usei essa solução em um problema que estava resolvendo em js, então pensei em compartilhar a tradução. Obrigado pela explicação e solução.

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

obrigado! solução incrível e definida como uma função pronta para começar!
Combate ao fogo com fogo

1

Variante C ++

Eu achei a solução do PenguinTD útil, então eu a portava para C ++, se alguém precisar:

remapear float (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

Porta PHP

Achei a solução do PenguinTD útil, então eu a transportei para o PHP. Fique a vontade!

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

Aqui está uma versão Javascript que retorna uma função que redimensiona para intervalos de origem e destino predeterminados, minimizando a quantidade de computação que deve ser feita a 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;
    }   
}

Aqui está um exemplo de uso dessa função para dimensionar 0-1 em -0x80000000, 0x7FFFFFFF

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

0

Proposta de atalho / simplificada

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

Wayne


1
O que tem NewRange/OldRangeaqui?
Zunair

0

Pessoalmente, uso a classe helper que suporta genéricos (compatível com 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) )
    }
}

0

Este exemplo converte a posição atual da música em um intervalo de ângulo de 20 a 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;
    }

0

Solução de liner de compreensão de lista

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

Versão mais longa

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

Versão Java

Sempre funciona, não importa como você o alimenta!

Deixei tudo expandido para facilitar o aprendizado. O arredondamento no final, é claro, é opcional.

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

}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.