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

vecaoik1  于 2022-12-17  发布在  其他
关注(0)|答案(6)|浏览(269)

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

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,我不知道如何计算输出值。
我相信这很简单,我现在无法思考:)
编辑:我只需要整数,没有分数或任何东西。

jhiyze9q

jhiyze9q1#

让我们忘掉数学,试着用直觉来解决这个问题。
首先,如果我们想把范围[ 0x ]中的输入数字Map到输出范围[ 0y ],我们只需要适当地缩放。0变成0,x变成y,数字t变成(y/x)*t
所以,让我们把你的问题简化为上面这个简单的问题。
输入范围[ input_startinput_end ]包含input_end - input_start + 1个数字,因此它等效于范围[ 0r ],其中r = input_end - input_start
同样,输出范围等于[ 0R ],其中R = output_end - output_start
输入input等于x = input - input_start,第一段中的值将转换为y = (R/r)*x,然后,我们可以通过添加output_starty值转换回原始输出范围:output = output_start + y .
这给我们:

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))
ryevplcw

ryevplcw2#

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

nsc4cvqm3#

其公式为
f(x)=(x -输入_开始)/(输入_结束-输入_开始)*(输出_结束-输出_开始)+输出_开始
我会在这里挂起这个帖子:https://betterexplained.com/articles/rethinking-arithmetic-a-visual-guide/,因为它帮助我很大,当试图想出这个直观的。一旦你明白了帖子是什么,它是微不足道的想出这些公式自己。注意,我曾经挣扎与这样的问题,以及。(我没有从属关系-只是发现它非常有用)
假设你有一个值域[input_start..input_end],让我们先把它归一化,0是input_start,1是input_end。这是一个简单的技巧,可以使问题变得更容易。
怎么做呢,我们要把所有的东西都左移一个input_start的量,这样如果input,x,正好是input_start,它应该给予零.
假设f(x)是执行转换的函数。

f(x) = x - input_start

让我们试试看:

f(input_start) = input_start - input_start = 0

适用于input_start
在这一点上,它还不适用于input_end,因为我们还没有缩放它。
让我们按范围的长度缩小它,然后我们将最大值(input_end)Map为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

你会看到方程的第一部分几乎都乘以了零,这样就抵消了所有的项,得到

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

正如您所看到的,它现在似乎已正确Map。

cigdeys3

cigdeys34#

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

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

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

yrefmtwq5#

保证将任何范围Map到任何范围

我编写了这个方法,它严格遵循将数字从一个范围Map到另一个范围的代数公式。计算使用双精度来保持精度,然后在最后,它将返回一个Double,其中包含您在方法参数中指定的小数位数。
没有必要将范围的下限和上限命名为lowhigh,因为无论范围的一端低于或高于另一端都没有区别,该方法仍将正确Map数字。
例如,如果您将任何范围指定为[-100至300]或[300至-100],则不会有任何区别,重新Map仍然会准确地输出。
以下是在代码中使用方法的方式:

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

下面是如何使用该方法的示例:
来源范围:-400至800
目的地范围:一万至三千五百
要重新Map的编号: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;

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

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(基于将int从0递增到100的for循环)重新Map到范围0到1,效果非常好。
虽然我现在知道我可以通过将增量从1更改为.01来创建循环,如下所示:

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

我只是为任何可能正在做类似于我正在做的事情的人指出这一点。这个方法按照描述的那样完美地工作。
:—)

kb5ga3dv

kb5ga3dv6#

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

它的作用是按比例找出输入在输入范围内的”深度“,然后将该比例应用于输出范围的大小,以绝对值的形式找出输出应该在输出范围内的深度,然后将输出范围的起点相加,得到实际的输出数。

相关问题