C语言 将一个数值范围映射到另一个数值范围


数学从来都不是我在学校的强项。
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,我不知道如何计算输出值。

我相信这很简单,我现在无法思考。

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

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

首先,如果我们想将[0, x]范围内的输入数字映射到[0, y]输出范围,我们只需要按适当的比例缩放。0归0,xy,数字t(y/x)*t

那么,让我们把你的问题简化成上面那个更简单的问题。

输入范围[input_start, input_end]包含input_end - input_start + 1个数字。所以它相当于一个范围[0, r],其中r = input_end - input_start .

同样,输出范围相当于[0, R],其中R = output_end - output_start .

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

这给了我们:

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

我将在这里链接这篇文章:https://betterexplained.com/articles/rethinking-arithmetic-a-visual-guide/,因为当我试图直观地提出这个问题时,它帮助了我很多。一旦你明白了这篇文章的意思,你自己想出这些公式就很简单了。请注意,我过去也曾为这类问题而挣扎。(我没有任何从属关系-只是发现它很有用)

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

我们怎么做呢?如果输入x恰好是input_start,那么它的值应该是0。

那么,假设f(x)是进行转换的函数

f(x) = x - input_start

让我们试一试:

f(input_start) = input_start - input_start = 0

适用于input_start .

此时,

还不能用于input_end,因为我们还没有缩放它。

让我们把它按范围的长度缩小,然后我们将把最大的值(input_end)映射到1。

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)

现在,实际上,我们差不多完成了,我们只需要把它移到右边,这样0就从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

你可以看到,方程的第一部分几乎乘以了0,因此所有的都消掉了,得到

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
如您所见,

现在似乎被正确地映射了。

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

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

保证任意范围映射到任意范围

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

没有必要将范围的低端和高端命名为lowhigh,因为无论一端比另一端低或高都没有区别,该方法仍然会正确映射数字。

例如,如果您将任何范围设置为[-100到300]或[300到-100],则不会产生任何差异。重新绘制的地图仍然会准确地出来。

下面是你在代码中如何使用这个方法:
mapOneRangeToAnother(myNumber, fromRangeA, fromRangeB, toRangeA, toRangeB, decimalPrecision)

下面是如何使用这个方法的一个例子:

源范围:-400 ~ 800
目的地范围:10000 ~ 3500
重新映射编号:250

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并删除decimalPrecision参数,然后将最后两行更改为:

int calcScale = (int) Math.pow(10, 0);
return (int) Math.round(finalNumber * calcScale) / calcScale;

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

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

,这里是方法:

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

在我的用例中,我需要淡出JavaFX控件的不透明度,但由于不透明度是一个从0到1的数字,我只是使用该方法将范围1到100(基于一个for循环,从0到100增加一个int)重新映射到范围0到1,它完美地工作了。

虽然我现在知道我可以通过将增量从1更改为。01来创建循环,就像这样:

for(double x=0; x<=1; x+=.01 {
    //Code to change controls opacity
}

我只是为那些可能正在做类似于我正在做的事情的人指出这一点。

:-)

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

它的作用是按比例找出输入"进入"输入范围的"多远"。然后将该比例应用于输出范围的大小,以绝对值计算出输出应该在输出范围内多远。然后将输出范围的起始位置相加,得到实际的输出数字。

相关内容

  • 没有找到相关文章

最新更新