将数值范围映射到另一个范围

数学从来就不是我在学校的强项

int input_start = 0;    // The lowest number of the range input.
int input_end = 254;    // The largest number of the range input.
int output_start = 500; // The lowest number of the range output.
int output_end = 5500;  // The largest number of the range output.


int input = 127; // Input value.
int output = 0;

如何将输入值转换为该范围的相应输出值?

例如,输入值“0”等于输出值“500”,输入值“254”等于输出值“5500”。如果输入值是50或101,我不知道如何计算输出值。

我相信这很简单,我现在不能思考:)

编辑: 我只需要整数,没有分数或任何东西。

129788 次浏览
output = ((input - input_start)/(input_end - input_start)) * (output_end - output_start) + output_start

它所做的就是按比例找出输入范围的“多远”。然后将这一比例应用于产出范围的大小,以绝对值计算出产出应该位于产出范围的多远。然后,它添加输出范围的开始,以获得实际的输出数字。

这里的关键点是在正确的位置进行整数除法(包括四舍五入)。到目前为止,没有一个答案的括号是正确的。正确的做法是:

int input_range = input_end - input_start;
int output_range = output_end - output_start;


output = (input - input_start)*output_range / input_range + output_start;

让我们忘掉数学,直观地解决这个问题。

首先,如果我们想要将范围[ 0x]中的输入数字映射到输出范围[ 0y]中,我们只需要按适当的量进行缩放。0变成0,x变成 y,数字 t变成 (y/x)*t

所以,让我们把你的问题简化成上面的简单问题。

An input range of [input_start, input_end] has input_end - input_start + 1 numbers. So it's equivalent to a range of [0, r], where r = input_end - input_start.

类似地,输出范围相当于[ 0R] ,其中 R = output_end - output_start

input的输入等价于 x = input - input_start。这,从第一段将翻译为 y = (R/r)*x。然后,我们可以通过添加 output_start: output = output_start + yy值转换回原来的输出范围。

This gives us:

output = output_start + ((output_end - output_start) / (input_end - input_start)) * (input - input_start)

或者,换种说法:

/* Note, "slope" below is a constant for given numbers, so if you are calculating
a lot of output values, it makes sense to calculate it once.  It also makes
understanding the code easier */
slope = (output_end - output_start) / (input_end - input_start)
output = output_start + slope * (input - input_start)

现在,这是 C,除法在 C 中截断,你应该尝试通过浮点计算得到一个更准确的答案:

double slope = 1.0 * (output_end - output_start) / (input_end - input_start)
output = output_start + slope * (input - input_start)

如果想要更加正确,您可以在最后一步中进行舍入而不是截断。您可以通过编写一个简单的 round函数来实现这一点:

#include <math.h>
double round(double d)
{
return floor(d + 0.5);
}

然后:

output = output_start + round(slope * (input - input_start))

Arduino 将此内置为 map

例如:

/* Map an analog value to 8 bits (0 to 255) */
void setup() {}


void loop()
{
int val = analogRead(0);
val = map(val, 0, 1023, 0, 255);
analogWrite(9, val);
}

该页面上还有实现:

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

分子式是

F (x) = (x-input _ start)/(input _ end-input _ start) * (output _ end- Output _ start) + output _ start

我将在这里挂起这个帖子: https://betterexplained.com/articles/rethinking-arithmetic-a-visual-guide/,因为它帮助了我很多,当试图想出这个直观。一旦你理解了这篇文章所说的内容,自己想出这些公式就显得微不足道了。请注意,我过去也曾为这类问题而苦苦挣扎。(我没有从属关系——只是觉得它很有用)

假设你有一个范围 [input_start..input_end],让我们从规范化它开始,这样0是 input_start,1是 input_end。这是一个简单的技术,使问题更容易。

我们要怎么做?我们必须把剩下的部分移动到 input _ start 金额上,这样如果输入 x 恰好是 input_start,它应该是零。

假设 f(x)是执行转换的函数。

f(x) = x - input_start

我们试试看:

f(input_start) = input_start - input_start = 0

input_start工作。

在这一点上,它还不适用于 input_end,因为我们还没有扩展它。

让我们按照范围的长度来缩放它,然后我们将把最大值(input _ end)映射到一。

f(x) = (x - input_start) / (input_end - input_start)

好,我们用 input_end试试。

f(input_end) = (input_end - input_start) / (input_end - input_start) = 1

太棒了,看起来很有效。

好的,下一步,我们将把它放大到输出范围。这与将输出范围的实际长度相乘一样微不足道,例如:

f(x) = (x - input_start) / (input_end - input_start) * (output_end - output_start)

now, actually, we're almost done, we just have to shift it to right so that 0 starts from output_start.

f(x) = (x - input_start) / (input_end - input_start) * (output_end - output_start) + output_start

让我们快速尝试一下。

f(input_start) = (input_start - input_start) / (input_end - input_start) * (output_end - output_start) + output_start

等式的第一部分基本上乘以零,这样就抵消了所有的东西

f(input_start) = output_start

再试试 input_end

f(input_end) = (input_end - input_start) / (input_end - input_start) * (output_end - output_start) + output_start

反过来又会变成:

f(input_end) = output_end - output_start + output_start = output_end

如你所见,它现在似乎被正确地映射了。

这保证将任何范围映射到任何范围

我写了这个方法,它精确地遵循了将一个范围的数字映射到另一个范围的代数公式。为了保持精度,计算使用了 Double,然后在结束时,它将返回一个 Double,其中包含您在方法参数中指定的小数位数。

函数的工作原理是这样的... 注意,我标记了范围 A 和 B 的末端——这是因为没有必要让你的范围开始低和结束高或类似的东西... 你可以使你的第一个范围300至 -7000和你的第二个范围 -3500至5500... 没有什么区别,你选择的数字范围,它会正确地映射它们。

mapOneRangeToAnother(myNumber, fromRangeA, fromRangeB, toRangeA, toRangeB, decimalPrecision)

下面是如何使用该函数的一个快速示例:

Source range: -400 to 800
目的地范围: 10000到3500
需要重新映射的号码: 250 < BR >

double sourceA = -400;
double sourceB = 800;
double destA = 10000;
double destB = 3500;
double myNum = 250;
        

double newNum = mapOneRangeToAnother(myNum,sourceA,sourceB,destA,destB,2);
    

Result: 6479.17

如果需要返回一个整数,只需告诉它返回小数点后0位的结果,然后将结果强制转换为 int,如下所示:

int myResult = (int) mapOneRangeToAnother(myNumber, 500, 200, -350, -125, 0);

或者可以声明函数返回一个 int,然后将最后一行从 return (double)改为 return (int)。

在 OPs 问题中,他们会这样使用函数:

int myResult = (int) mapOneRangeToAnother(input, input_start, input_end, output_start, output_end, 0);

and here is the function:

public static double mapOneRangeToAnother(double sourceNumber, double fromA, double fromB, double toA, double toB, int decimalPrecision ) {
double deltaA = fromB - fromA;
double deltaB = toB - toA;
double scale  = deltaB / deltaA;
double negA   = -1 * fromA;
double offset = (negA * scale) + toA;
double finalNumber = (sourceNumber * scale) + offset;
int calcScale = (int) Math.pow(10, decimalPrecision);
return (double) Math.round(finalNumber * calcScale) / calcScale;
}

我甚至在一个计数器中使用这个函数来淡出屏幕上的一些东西,但是由于不透明度是一个介于0和1之间的值,我设置了一个计数器从0到100,然后我映射范围0-100到0-1,它得到所有介于0和1之间的分数,基于输入数字0到100... 这是一个更简单的方法,因为主代码看起来更好使用这样的一个函数比它实际上做运算。

您的英里数可能会有所不同... 享受! : -)