# Convert a number range to another range, maintaining ratio

•
•  |
•

### Question

I'm trying to convert one range of numbers to another, maintaining ratio. Maths is not my strong point.

I have an image file where point values may range from -16000.00 to 16000.00 though the typical range may be much less. What I want to do is compress these values into the integer range 0-100, where 0 is the value of the smallest point, and 100 is the value of the largest. All points in between should keep a relative ratio even though some precision is being lost I'd like to do this in python but even a general algorithm should suffice. I'd prefer an algorithm where the min/max or either range can be adjusted (ie, the second range could be -50 to 800 instead of 0 to 100).

### Solution

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

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

Or if you want to protect for the case where the old range is 0 (OldMin = OldMax):

``````OldRange = (OldMax - OldMin)
if (OldRange == 0)
NewValue = NewMin
else
{
NewRange = (NewMax - NewMin)
NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin
}
``````

Note that in this case we're forced to pick one of the possible new range values arbitrarily. Depending on context, sensible choices could be: `NewMin` (see sample), `NewMax` or `(NewMin + NewMax) / 2`

### OTHER TIPS

That's a simple linear conversion.

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

So converting 10000 on the scale of -16000 to 16000 to a new scale of 0 to 100 yields:

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

Actually there are some cases that above answers would break. Such as wrongly input value, wrongly input range, negative input/output ranges.

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

There is a condition, when all of the values that you are checking are the same, where @jerryjvl's code would return NaN.

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

I didn't dig up the BNF for this, but the Arduino documentation had a great example of the function and it's breakdown. I was able to use this in Python by simply adding a def renaming to remap (cause map is a built-in) and removing the type casts and curly braces (ie just remove all the 'long's).

Original

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

In the listing provided by PenguinTD, I do not understand why the ranges are reversed, it works without having to reverse the ranges. Linear range conversion is based upon the linear equation `Y=Xm+n`, where `m` and `n` are derived from the given ranges. Rather than refer to the ranges as `min` and `max`, it would be better to refer to them as 1 and 2. So the formula would be:

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

Where `Y=y1` when `X=x1`, and `Y=y2` when `X=x2`. `x1`, `x2`, `y1` & `y2` can be given any `positive` or `negative` value. Defining the expression in a macro makes it more useful,it can then be used with any argument names.

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

The `float` cast would ensure floating point division in the case where all the arguments are `integer` values. Depending on the application it may not be necessary to check the ranges `x1=x2` and `y1==y2`.

PHP Port

``````/**
* =====================================
*              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;
}
``````

I used this solution in a problem I was solving in js, so I thought I would share the translation. Thanks for the explanation and solution.

``````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++ Variant

I found PenguinTD's Solution usefull, so i ported it to C++ if anyone needs it:

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

Here's some short Python functions for your copy and paste ease, including a function to scale an entire list.

``````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]
``````

Which can be used like so:

``````scale_list([1,3,4,5], 0, 100)
``````

[0.0, 50.0, 75.0, 100.0]

In my case I wanted to scale a logarithmic curve, like so:

``````scale_list([math.log(i+1) for i in range(5)], 0, 50)
``````

[0.0, 21.533827903669653, 34.130309724299266, 43.06765580733931, 50.0]

Short-cut/simplified proposal

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

wayne

I personally use the helper class which supports generics (Swift 3 compatible)

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

This example converts a songs current position into an angle range of 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;
}
``````

Here is a Javascript version that returns a function that does the rescaling for predetermined source and destination ranges, minimizing the amount of computation that has to be done each time.

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

Here is an example of using this function to scale 0-1 into -0x80000000, 0x7FFFFFFF

``````let normTo32Fn = makeRangeMapper(0, 1, -0x80000000, 0x7FFFFFFF);
let fs = normTo32Fn(0.5);
let fs2 = normTo32Fn(0);
``````

# List comprehension one liner solution

``````color_array_new = [int((((x - min(node_sizes)) * 99) / (max(node_sizes) - min(node_sizes))) + 1) for x in node_sizes]
``````

# Longer version

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