비율을 유지하면서 숫자 범위를 다른 범위로 변환


256

비율을 유지하면서 한 숫자 범위를 다른 숫자 범위로 변환하려고합니다. 수학은 나의 장점이 아닙니다.

포인트 범위는 -16000.00 ~ 16000.00 범위의 이미지 파일이 있지만 일반적인 범위는 훨씬 적습니다. 내가하고 싶은 것은이 값을 0-100의 정수 범위로 압축하는 것입니다. 여기서 0은 가장 작은 점의 값이고 100은 가장 큰 값입니다. 사이의 모든 점은 일부 정밀도가 손실 되더라도 상대적인 비율을 유지해야합니다. 파이썬에서는이 작업을 수행하고 싶지만 일반 알고리즘조차 충분합니다. 최소 / 최대 또는 두 범위 중 하나를 조정할 수있는 알고리즘을 선호합니다 (즉, 두 번째 범위는 0 ~ 100 대신 -50 ~ 800 일 수 있음).


클레 투스가 먼저 답장을 보냈기 때문에 클 레터에게 답을 드리겠습니다.
SpliFF

2
실제로 미안한 cletus, 나는 그가 새롭고 포인트가 필요하기 때문에 Jerry에게 그것을주고 있습니다.
SpliFF

2
이건 에이지주의입니다! Heheh, j / k, 걱정 마세요. :)
cletus

7
이 질문은 어떻게 stackoverflow 질문 클로저 여단을 피했습니까? ;)
thanikkal

답변:


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

또는 좀 더 읽기 쉽습니다.

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

또는 이전 범위가 0 인 경우 ( OldMin = OldMax ) 를 보호하려는 경우 :

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

이 경우 가능한 새로운 범위 값 중 하나를 임의로 선택해야합니다. 상황에 따라 합리적인 선택이 될 수있다 : NewMin( 샘플 참조 ), NewMax또는(NewMin + NewMax) / 2


oldMax가 16000이어야합니까 아니면 이전 포인트 세트 (예 : 15034.00)에서 가장 높은 값일 수 있습니까?
SpliFF

5
원하는 것을 무엇이든 만들 수 있습니다 ... 범위 중 하나가 다른 범위에 비해 매우 작은 경우 이상한 결과를 얻을 수 있음을 명심하십시오 (정확하지는 않지만 크기의 차이가 1000000 이상인 경우 범위, 실제로 예상대로 동작하는지 확인하거나 부동 소수점 부정확성에 대해 배우십시오)
jerryjvl

2
이 답변의 인기를 고려할 때보 다 일반적인 경우 OldMax == OldMin 가능성을 고려해야합니다. 이로 인해 0으로 나눌 수 있습니다.
사용자

3
대단해. 이 변환에 수학 이름이 있습니까?
Tarik


65

그것은 간단한 선형 변환입니다.

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

따라서 -16000에서 16000까지의 스케일에서 10000을 0에서 100까지의 새로운 스케일로 변환하면 다음과 같습니다.

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
이것은 잘못이다. 나누기 전에 Old Value에서 Old Min을 빼야합니다.
SPWorley

20

실제로 위의 답변이 깨지는 경우가 있습니다. 잘못된 입력 값, 잘못된 입력 범위, 부정적인 입력 / 출력 범위와 같은.

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

확인하는 모든 값이 동일한 경우 @jerryjvl의 코드가 NaN을 반환하는 조건이 있습니다.

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

5

나는 이것을 위해 BNF 를 파헤 치지 않았지만 Arduino 문서에는 그 기능에 대한 훌륭한 예가 있었고 그 고장이 있습니다. 파이썬에서 def renaming을 다시 매핑 (map이 내장되어 있기 때문에)을 추가하고 유형 캐스트와 중괄호를 제거 (즉, 모든 'long'을 제거)하여 이것을 사용할 수있었습니다.

실물

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

파이썬

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

PenguinTD가 제공 한 목록에서 범위가 반전 된 이유를 이해하지 못하므로 범위를 바꾸지 않고도 작동합니다. 선형 범위 변환은 선형 방정식에 기초하는 Y=Xm+n, m그리고 n주어진 범위로부터 유도된다. 범위를 minand 로 참조하는 대신 max1과 2로 참조하는 것이 좋습니다. 따라서 수식은 다음과 같습니다.

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

어디 Y=y1X=x1, 그리고 Y=y2X=x2. x1, x2, y1y2특정 수 positive또는 negative값. 매크로에서 표현식을 정의하면 더 유용하므로 인수 이름과 함께 사용할 수 있습니다.

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

float캐스트는 모든 인수가있는 경우에는 소수점 부문 부동 보장 할 integer값. 응용 프로그램에 따라이 범위를 체크 할 필요가 없습니다 x1=x2y1==y2.


감사! C # 변환은 다음과 같습니다. float RangeConv(float input, float x1, float x2, float y1, float y2) { return (((input - x1) * (y2 - y1)) / (x2 - x1)) + y1; }
Zunair

2

다음은 전체 목록을 확장하는 기능을 포함하여 복사 및 붙여 넣기 용이성을위한 간단한 Python 함수입니다.

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]

다음과 같이 사용할 수 있습니다.

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

[0.0, 50.0, 75.0, 100.0]

제 경우에는 다음과 같이 로그 곡선을 스케일하고 싶었습니다.

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

[0.0, 21.533827903669653, 34.130309724299266, 43.06765580733931, 50.0]


1

js에서 해결하는 문제 에이 솔루션을 사용 했으므로 번역을 공유 할 것이라고 생각했습니다. 설명과 해결책에 감사드립니다.

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

감사합니다! 멋진 솔루션과 바로 사용할 수있는 기능으로 설정!
불로 불 싸움

1

C ++ 변형

PenguinTD의 솔루션이 유용하다는 것을 알았습니다. 그래서 누군가가 필요하면 C ++로 포팅했습니다.

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 포트

PenguinTD의 솔루션이 도움이 되었기 때문에 PHP로 포팅했습니다. 알아서 드세요!

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

다음은 미리 결정된 소스 및 대상 범위에 대한 재조정을 수행하는 함수를 반환하는 Javascript 버전으로, 매번 수행해야하는 계산량을 최소화합니다.

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

다음은이 함수를 사용하여 0-1을 -0x80000000, 0x7FFFFFFF로 스케일링하는 예입니다.

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

0

바로 가기 / 간체 제안

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

웨인


1
NewRange/OldRange여기 가 무엇입니까 ?
Zunair

0

나는 제네릭을 지원하는 도우미 클래스를 개인적으로 사용합니다 (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

이 예제는 노래의 현재 위치를 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;
    }

0

이해력있는 하나의 라이너 솔루션

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

더 긴 버전

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

자바 버전

무엇을 먹이든 항상 작동합니다!

배우기 쉽게 따라갈 수 있도록 모든 것을 확장했습니다. 물론 마지막 반올림은 선택 사항입니다.

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

}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.