2009-05-30 4 views
177

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

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

+0

는 모두 감사 , 나는 간다. g는 첫 번째로 들어 왔기 때문에 cletus에 대한 대답이고, 나의 followup에 대답하기 위해 +1로 제리에게 대답했다. – SpliFF

+1

실제 미안해. 크레타스. 새롭고 점수가 필요하기 때문에 제리에게주고있다. – SpliFF

+1

안녕하세요 노년교입니다! Heheh, j/k, 걱정 마라. :) – cletus

답변

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

또는를 좀 더 읽기 :

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

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

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

+0

oldMax는 16000이어야합니까? 예를 들어 구식 세트 (예 : 15034.00)에서 가장 높은 값이 될 수 있습니까? 구별이 중요합니까? – SpliFF

+5

원하는대로 만들 수 있습니다. 범위 중 하나가 다른 것에 비해 매우 작 으면 이상한 결과가 발생할 수 있음을 명심하십시오 (정확하지는 않지만, 크기 사이에 1000000 개 이상의 요소 차이가있을 경우 당신이 예상했던대로 실제로 동작하는지 확인하십시오. 또는 부동 소수점 부정확성에 대해 배웁니다.) – jerryjvl

+1

이 답변의 인기를 고려할 때 좀 더 일반적인 경우 OldMax == OldMin 가능성을 고려해야합니다. 0으로 나누기. 여기 C에서 # – Medorator

49

간단한 선형 변환입니다.

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

그래서 100 개 수익률 0의 새로운 규모로 16000에 -16000의 규모 만 변환 :

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 Min에서 Old Min을 뺄 필요가 있습니다. – SPWorley

+21

음, 나는 ...... – cletus

10

는 상태가 확인하려는 모든 값이 @의 jerryjvl의 코드가 NaN을 반환하는 경우, 동일한 경우.

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

실제로 위의 답변이 중단되는 경우가 있습니다. 잘못 입력 된 값, 잘못 입력 된 범위, 음수 입/출력 범위 등.

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

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

감사합니다! 멋진 솔루션과 갈 준비가 된 기능으로 설정하십시오! –

1

C++ 변형

나는 PenguinTD의 솔루션 유용한 발견, 그래서 누군가가 그것을 필요로하는 경우 나 C++로 포팅 :

플로트 다시 매핑을 (플로트 x는, oMin을 떠, OMAX을 떠, n 분을 떠 , PenguinTD에 의해 제공되는 목록에서 {)

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

을 Nmax 개의 플로트, 나는 유엔하지 않습니다 왜 범위가 바뀌 었는지 알기 때문에 범위를 뒤집지 않고도 작동합니다. 선형 범위 변환은 선형 방정식 Y=Xm+n을 기반으로하며 여기서 mn은 주어진 범위에서 파생됩니다. 범위를 minmax으로 표시하는 대신 1과 2로 참조하는 것이 좋습니다.그래서 공식은 다음과 같습니다

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

경우 Y=y1X=x1Y=y2X=x2에게. x1, x2, 은 임의의 positive 또는 negative 값이 주어질 수있다. 매크로에서 표현식을 정의하면 더 유용하게 사용할 수 있으며, 그런 다음 모든 인수 이름과 함께 사용할 수 있습니다.

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

float 캐스트는 모든 인수가 integer 값 어디에 경우 포인트 부문 부동 보장한다. 응용 프로그램에 따라 범위 x1=x2y1==y2을 확인하지 않아도됩니다.

+0

고마워! _here는 C# 1 변환이다 _ 'RangeConv가 { 창 (((입력 (Input 플로트 X1, 플로트 X2, 플로트 Y1, 플로트 Y2 플로트) 부동 - X1) * (Y2 - Y1))/(X2를 - x1)) + y1; }' – Zunair

0

짧은 컷/단순화 제안

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

웨인

+0

여기에'NewRange/OldRange' 란 무엇입니까? – Zunair

2

PHP 포트

그래서 PHP로 포팅 PenguinTD의 솔루션이 도움이되었다고합니다. 알아서 드세요!

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

감사합니다. 매우 도움이되었습니다. – dearsina

1

전체 목록을 확장하는 기능을 포함하여 복사 및 붙여 넣기가 용이 한 간단한 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]

0

나는 개인적으로 제네릭 (스위프트 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)) 
    } 
} 
2

나는이에 대한 BNF을 발굴하지 않았다을 지원하는 도우미 클래스를 사용 그러나 Arduino 문서에는 기능의 훌륭한 예가 있었으며 고장입니다. 파이썬에서 이것을 재사용하기 위해 단순히 이름 바꾸기를 추가하는 것만으로도 사용할 수있었습니다. (원인 맵은 내장되어 있습니다.) 캐스트와 중괄호를 제거했습니다. 즉 모든 '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; 
} 

파이썬이 예 (20)의 각도 범위로 노래 현재 위치를 변환

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

- 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; 
    } 
관련 문제