将数字范围转换为另一个范围,保持比例

我试图将一个数字范围转换为另一个范围,保持比例。 math不是我的强项。

我有一个图像文件,其中点值可能范围从-16000.00至16000.00,虽然典型的范围可能会less得多。 我想要做的是将这些值压缩到0-100的整数范围内,其中0是最小点的值,100是最大值。 所有点之间应该保持一个相对比例,即使有一些精度正在失去我想在Python中做到这一点,但即使是一般的algorithm应该足够。 我更喜欢一个algorithm,其中最小/最大或任一范围可以调整(即第二个范围可以是-50到800,而不是0到100)。

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 } 

请注意,在这种情况下,我们不得不任意select一个可能的新范围值。 根据上下文,明智的select可能是: NewMin参见示例 ), NewMax(NewMin + NewMax) / 2

这是一个简单的线性转换。

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

因此,将-16000到10000的范围内的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 

其实有些情况下,上面的答案会打破。 如错误的input值,错误的input范围,负input输出范围。

 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 

有一个条件,当你检查的所有值都是相同的,@ jerryjvl的代码将返回NaN。

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

在PenguinTD提供的列表中,我不明白为什么这个范围是相反的,它的工作原理是不需要颠倒范围。 线性范围转换基于线性方程Y=Xm+n ,其中mn是从给定的范围导出的。 而不是将范围称为minmax ,最好将它们称为1和2.所以公式是:

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

X=x1Y=y1 ;当X=x2时, Y=y2x1x2y1y2可以给出任何positivenegative 。 在macros中定义expression式使其更有用,然后可以将其用于任何参数名称。

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

在所有参数都是integer数值的情况下, float将确保浮点除法。 取决于应用程序,可能不需要检查范围x1=x2y1==y2

PHP端口

发现PenguinTD的解决scheme很有帮助,所以我把它移植到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; } 

我没有为此发掘BNF ,但是Arduino文档有一个很好的例子,它的function是崩溃的。 我能够通过简单地添加一个def重命名重新映射(因为map是一个内置的),并删除types转换和花括号(即只是删除所有'长'),在Python中使用它。

原版的

 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

我在js中解决的一个问题中使用了这个解决scheme,所以我想我会分享这个翻译。 感谢您的解释和解决scheme。

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

C ++变体

我发现PenguinTD的解决scheme有用,所以我把它移植到C ++,如果有人需要它:

浮动重映射(浮动x,浮动oMin,浮动oMax,浮动nMin,浮动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; } 

下面是一些简短的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]

我个人使用支持generics的助手类(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) ) } } 

本示例将歌曲当前位置转换为20-40的angular度范围。

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

捷径/简化build议

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

韦恩