将数字范围转换为另一个范围,保持比例
- 2025-01-03 08:41:00
- admin 原创
- 114
问题描述:
我正在尝试将一个数字范围转换为另一个数字范围,保持比例。数学不是我的强项。
我有一个图像文件,其中点值的范围可能从 -16000.00 到 16000.00,尽管典型范围可能要小得多。我想要做的是将这些值压缩到整数范围 0-100 中,其中 0 是最小点的值,100 是最大值。即使丢失了一些精度,中间的所有点都应保持相对比率。我想在 python 中执行此操作,但即使是通用算法也足够了。我更喜欢可以调整最小值/最大值或任一范围的算法(即,第二个范围可以是 -50 到 800,而不是 0 到 100)。
解决方案 1:
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
解决方案 2:
这是一个简单的线性转换。
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
解决方案 3:
实际上,在某些情况下上述答案会失效。例如输入错误的值、输入错误的范围、负的输入/输出范围。
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
解决方案 4:
这里有一些简短的 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]
解决方案 5:
我没有为此挖掘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
解决方案 6:
存在一种情况,当您检查的所有值都相同时,@jerryjvl 的代码将返回 NaN。
if (OldMin != OldMax && NewMin != NewMax):
return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
else:
return (NewMax + NewMin) / 2
解决方案 7:
使用Numpy
和interp
函数,您可以将值从旧范围转换为新范围:
>>> 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])
解决方案 8:
添加了带有数学解释的 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);
解决方案 9:
我个人使用支持泛型的辅助类(兼容 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
解决方案 10:
我在用 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;
}
解决方案 11:
在 PenguinTD 提供的列表中,我不明白为什么范围是反转的,它不需要反转范围就可以工作。线性范围转换基于线性方程Y=Xm+n
,其中m
和n
是从给定的范围得出的。与其将范围称为min
和max
,不如将它们称为 1 和 2。因此公式将是:
Y = (((X - x1) * (y2 - y1)) / (x2 - x1)) + y1
其中Y=y1
当X=x1
、 和Y=y2
当X=x2
. x1
、x2
、y1
&y2
可以赋予任何positive
或negative
值。在宏中定义表达式使其更有用,然后可以将其与任何参数名称一起使用。
#define RangeConv(X, x1, x2, y1, y2) (((float)((X - x1) * (y2 - y1)) / (x2 - x1)) + y1)
float
在所有参数都是值的情况下,强制类型转换将确保浮点除法。integer
根据应用程序的不同,可能不需要检查范围x1=x2
和y1==y2
。
解决方案 12:
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;
}
解决方案 13:
这是一个 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);
解决方案 14:
列表理解一行解决方案
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
解决方案 15:
我在 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)
}
}
解决方案 16:
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);
}
解决方案 17:
我发现 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;
}
解决方案 18:
此示例将歌曲的当前位置转换为 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;
}
解决方案 19:
接受的答案(https://stackoverflow.com/a/929107/413127)作为 Kotlin 辅助函数:
fun normalize(from: ClosedFloatingPointRange<Float>, to: ClosedFloatingPointRange<Float>, value: Float): Float {
val oldRange = from.endInclusive - from.start
val newRange = to.endInclusive - to.start
return (((value - from.start) * newRange) / oldRange) + to.start
}
val newValueFloat = normalize(from = 0f..500f, to = 0f..1f, value = 420f) // = 1.4
^ 使用所有浮点数
fun normalize(from: IntRange, to: IntRange, value: Int): Int {
val oldRange = from.last - from.first
val newRange = to.last - to.first
return (((value - from.first) * newRange) / oldRange) + to.first
}
val newValueInt = normalize(from = 0..500, to = 0..1, value = 420) // = 1
^ 使用所有整数
fun normalize(from: IntRange, to: IntRange, value: Float): Float {
val oldRange = from.last.toFloat() - from.first.toFloat()
val newRange = to.last.toFloat() - to.first.toFloat()
return (((value - from.first) * newRange) / oldRange) + to.first
}
val newValueFloat = normalize(from = 0..500, to = 0..1, value = 420f) // = 1.4
^ 使用 Int 范围但结果为 Float
解决方案 20:
用一个经常需要的范围转换的例子来说明@cletus
的观点——温度从摄氏温度到华氏温度,但使用@cletus
高低公式来动态计算缩放因子,而不是使用硬编码常数。
function c2f(_) {
return \n sprintf(" %+23.17f °C => %+23.17f °F", _,
_ * (((_ = length("___")) + _)^_ - \n _ * _ * ++_) / (_-- + _ * (_ += _^_+_--)) + _)
}
1 -39.27855711422846241 °C => -38.70140280561123802 °F
2 -17.99599198396796496 °C => -0.39278557114233337 °F
3 +3.28657314629257602 °C => +37.91583166332663524 °F
4 +24.56913827655313298 °C => +76.22444889779563937 °F
5 +45.85170340681356294 °C => +114.53306613226440902 °F
6 +67.13426853707397868 °C => +152.84168336673315025 °F
7 +88.41683366733475680 °C => +191.15030060120255939 °F
8 +109.69939879759553492 °C => +229.45891783567196853 °F
9 +130.98196392785618514 °C => +267.76753507014109346 °F
您可能会注意到代码本身并不使用212
并32
计算新的范围。
为什么要费心处理这两个看似任意的值,而实际上华氏度的沸点/凝固点具有一个很少有人提及的非常有趣的特性:
freezing pt = 32 °F = 6^2 - 4 boiling pt = 212 °F = 6^3 - 4
效果-4
相互抵消,因此范围只是
6-cubed 6^3 = 216 6 * 6 * 6 - 6-squared 6^2 = 36 6 * 6 * (1) <—-- implicit ---------------------- ------------------------------- = 180 (6 * 6 * (6-1) = 6 * (6*5) 6 * 30 = 180
简化180 / 100
=> 18 / 10
=> 9 / 5
。
我不知道这些near-powers-of-6
属性是故意选择的,还是仅仅是巧合,我们可以利用它来辅助心算,而不用处理像5/9
)F -> C
这样的可怕分数
1. 180 x celsius-value # new-max - new-min
2. move decimal point over by 2 spots # old-max - old-min
3. finally, add back the new-range-min of 32 # new-min
180
中的base-6
是500
,与上面得出的系数相匹配。我不熟悉,python
所以我用与语言无关的方式编写了它。
解决方案 21:
快捷/简化提案
NewRange/OldRange = Handy multiplicand or HM
Convert OldValue in OldRange to NewValue in NewRange =
(OldValue - OldMin x HM) + NewMin