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

2025-01-03 08:41:00
admin
原创
114
摘要:问题描述:我正在尝试将一个数字范围转换为另一个数字范围,保持比例。数学不是我的强项。我有一个图像文件,其中点值的范围可能从 -16000.00 到 16000.00,尽管典型范围可能要小得多。我想要做的是将这些值压缩到整数范围 0-100 中,其中 0 是最小点的值,100 是最大值。即使丢失了一些精度,中间...

问题描述:

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

我有一个图像文件,其中点值的范围可能从 -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:

使用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])

解决方案 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,其中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)

float在所有参数都是值的情况下,强制类型转换将确保浮点除法。integer根据应用程序的不同,可能不需要检查范围x1=x2y1==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

您可能会注意到代码本身并不使用21232计算新的范围。

为什么要费心处理这两个看似任意的值,而实际上华氏度的沸点/凝固点具有一个很少有人提及的非常有趣的特性:

        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-6500,与上面得出的系数相匹配。我不熟悉,python所以我用与语言无关的方式编写了它。

解决方案 21:

快捷/简化提案

 NewRange/OldRange = Handy multiplicand or HM
 Convert OldValue in OldRange to NewValue in NewRange = 
 (OldValue - OldMin x HM) + NewMin
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用