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

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

我有一个图像文件,其中点值的范围可能从-16000.00到16000.00,尽管典型的范围可能要小得多。我要做的是将这些值压缩到0-100的整数范围内,其中0是最小点的值,100是最大点的值。在两者之间的所有点应该保持一个相对的比例,即使一些精度正在丢失,我想在python中这样做,但即使是一个通用的算法应该足够了。我更喜欢最小/最大或任何一个范围都可以调整的算法(即,第二个范围可以是-50到800,而不是0到100)。

309194 次浏览

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

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

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

有一种情况,当您检查的所有值都相同时,@jerryjvl的代码将返回NaN。

if (OldMin != OldMax && NewMin != NewMax):
return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
else:
return (NewMax + NewMin) / 2
实际上,在某些情况下,上述答案会出错。 例如错误的输入值,错误的输入范围,负的输入/输出范围
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

我在一个用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;
}

c++变体

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

在由PenguinTD提供的清单中,我不明白为什么范围是颠倒的,它不需要颠倒范围就能工作。线性范围转换基于线性方程Y=Xm+n,其中mn是从给定的范围推导出来的。与其将范围称为minmax,不如将它们称为1和2。所以公式是:

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

其中Y=y1X=x1时,Y=y2X=x2时。x1x2y1 &y2可以赋给任何positivenegative值。在宏中定义表达式使其更有用,它可以与任何参数名称一起使用。

#define RangeConv(X, x1, x2, y1, y2) (((float)((X - x1) * (y2 - y1)) / (x2 - x1)) + y1)
在所有实参都是integer值的情况下,float强制转换将确保浮点除法。 根据应用程序的不同,可能不需要检查x1=x2y1==y2.

捷径/简化方案

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

韦恩

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

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

我个人使用支持泛型的helper类(Swift 3,4)。x兼容)

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

例:

   let rescaler = Rescale<Float>(from: (-1, 1), to: (0, 100))
    

print(rescaler.rescale(0)) // OUTPUT: 50

我没有为此挖掘BNF,但Arduino文档有一个很好的函数示例和它的分解。我可以在Python中通过简单地添加一个def重命名到remap(因为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;
}

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

这个例子将歌曲的当前位置转换为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;
}

下面是一个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);

列出理解一行的解决方案

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

Java版本

不管你喂它什么,它都能工作!

我把所有内容都展开了,这样便于学习。当然,最后舍入是可选的。

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


}

增加了KOTLIN版本的数学解释

假设我们有一个介于(OMin Omax)和这个范围内的值X之间的刻度

我们想把它转换为(NMin NMax)

我们知道X,我们需要找到Y,比值必须相等:

 => (Y-NMin)/(NMax-NMin) = (X-OMin)/(OMax-OMin)
      

=>  (Y-NMin)/NewRange = (X-OMin)/OldRange


=>   Y = ((X-OMin)*NewRange)/oldRange)+NMin  Answer
   

从实用主义的角度来看,我们可以这样写这个问句:

 private fun  convertScale(oldValueToConvert:Int): Float {
// Old Scale 50-100
val oldScaleMin = 50
val oldScaleMax = 100
val oldScaleRange= (oldScaleMax - oldScaleMin)


//new Scale 0-1
val newScaleMin = 0.0f
val newScaleMax = 1.0f
val newScaleRange=  (newScaleMax - newScaleMin)
     

return ((oldValueToConvert - oldScaleMin)* newScaleRange/ oldScaleRange) + newScaleMin
}

JAVA

/**
*
* @param x
* @param inMin
* @param inMax
* @param outMin
* @param outMax
* @return
*/
private long normalize(long x, long inMin, long inMax, long outMin, long outMax) {
long outRange = outMax - outMin;
long inRange  = inMax - inMin;
return (x - inMin) *outRange / inRange + outMin;
}

用法:

float brightness = normalize(progress, 0, 10, 0,255);

我写了一个函数用R来做这个,方法和上面一样,但是我需要在R中做很多次,所以我想分享一下,以防它对任何人有帮助。

convertRange <- function(
oldValue,
oldRange = c(-16000.00, 16000.00),
newRange = c(0, 100),
returnInt = TRUE # the poster asked for an integer, so this is an option
){
oldMin <- oldRange[1]
oldMax <- oldRange[2]
newMin <- newRange[1]
newMax <- newRange[2]
newValue = (((oldValue - oldMin)* (newMax - newMin)) / (oldMax - oldMin)) + newMin
  

if(returnInt){
return(round(newValue))
} else {
return(newValue)
}
}

使用Numpyinterp函数,你可以将您的值从旧范围转换为新范围:

>>> import numpy as np
>>> np.interp(0, [-16000,16000], [0,100])
50.0

你也可以尝试映射值列表:

>>> np.interp([-16000,0,12000] ,[-16000,16000], [0,100])
array([ 0. , 50. , 87.5])