将数值范围映射到另一个

Joe*_*Joe 58 c math arduino

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

int input_start = 0; // The lowest number of the range input.
int input_end = 254; // The lowest 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 ouput.

int input = 127; // Input value.
int output = 0;
Run Code Online (Sandbox Code Playgroud)

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

例如,输入值"0"将等于输出值"500",输入值"254"将等于输出值"5500".如果输入值为50或101,我无法弄清楚如何计算输出值.

我确定这很简单,我现在想不到:)

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

Alo*_*hal 106

让我们忘记数学并尝试直观地解决这个问题.

首先,如果我们想要将[ 0,x] 范围内的输入数字映射到输出范围[ 0,y],我们只需要按适当的数量进行缩放.0变为0,x转到y,并且数字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.然后,我们可以y通过添加output_start以下内容将值转换回原始输出范围:output = output_start + y.

这给了我们:

output = output_start + ((output_end - output_start) / (input_end - input_start)) * (input - input_start)
Run Code Online (Sandbox Code Playgroud)

或者,另一种方式:

/* 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)
Run Code Online (Sandbox Code Playgroud)

现在,这是C和C截断中的除法,您应该尝试通过计算浮点数来获得更准确的答案:

double slope = 1.0 * (output_end - output_start) / (input_end - input_start)
output = output_start + slope * (input - input_start)
Run Code Online (Sandbox Code Playgroud)

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

#include <math.h>
double round(double d)
{
    return floor(d + 0.5);
}
Run Code Online (Sandbox Code Playgroud)

然后:

output = output_start + round(slope * (input - input_start))
Run Code Online (Sandbox Code Playgroud)

  • 这假设是线性映射。如果您想要正弦曲线、抛物线、指数、对数、反抛物线、椭圆形或圆形,我已经开发了一个[包含这些公式的 Desmos 图](https://www.desmos.com/calculator/ewnq4hyrbz)。 (9认同)
  • 在过去的几十年中,我至少写过几次这个函数,但是这种解释比我自己能提供的更好。+1用于消除鸿沟-每次运行可进行650万次计算。 (2认同)
  • 这是我在此站点上看到的最佳答案之一。谢谢! (2认同)

Dus*_*tin 22

Arduino有这个内置的地图.

例:

/* 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);
}
Run Code Online (Sandbox Code Playgroud)

它还在该页面上实现:

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 仅供参考,他们的`map`功能已被破坏.它是通过简单地将所有参数更改为int而从浮点函数转换而来的,并且它具有破坏的分布. (8认同)

Sve*_*ach 11

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

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

output = (input - input_start)*output_range / input_range + output_start;
Run Code Online (Sandbox Code Playgroud)


Ert*_*maa 11

公式是

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_start1,而1为input_end.这是使问题更容易的简单技术.

我们怎么做?我们将不得不将input_start数量留下的所有内容移位,这样如果输入x恰好是input_start,则应该给出零.

所以,让我们说f(x)是进行转换的功能.

f(x) = x - input_start
Run Code Online (Sandbox Code Playgroud)

让我们试一试:

f(input_start) = input_start - input_start = 0
Run Code Online (Sandbox Code Playgroud)

适用于input_start.

在这一点上,它还input_end没有用,因为我们还没有扩展它.

让我们按照范围的长度缩小它,然后我们将最大值(input_end)映射到一个.

f(x) = (x - input_start) / (input_end - input_start)
Run Code Online (Sandbox Code Playgroud)

好吧,让我们试一试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)
Run Code Online (Sandbox Code Playgroud)

现在,实际上,我们差不多完成了,我们只需要将它移到右边,这样0就从output_start开始.

f(x) = (x - input_start) / (input_end - input_start) * (output_end - output_start) + output_start
Run Code Online (Sandbox Code Playgroud)

让我们快点试试吧.

f(input_start) = (input_start - input_start) / (input_end - input_start) * (output_end - output_start) + output_start
Run Code Online (Sandbox Code Playgroud)

你看到等式的第一部分几乎乘以零,从而取消了所有的东西,给你

f(input_start) = output_start
Run Code Online (Sandbox Code Playgroud)

我们也试试吧input_end.

f(input_end) = (input_end - input_start) / (input_end - input_start) * (output_end - output_start) + output_start
Run Code Online (Sandbox Code Playgroud)

反过来最终会:

f(input_end) = output_end - output_start + output_start = output_end
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,它现在似乎正确映射.


Mic*_*ims 10

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

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

无需将范围的低端和高端命名为低端高端,因为如果一端低于或高于任一范围的另一端,则没有区别,该方法仍将正确映射数字。

例如,如果您将任何范围指定为 [-100 到 300] 或 [300 到 -100],则不会有任何区别。重新映射仍然会准确地显示出来。

以下是在代码中使用该方法的方法:

mapOneRangeToAnother(myNumber, fromRangeA, fromRangeB, toRangeA, toRangeB, decimalPrecision)
Run Code Online (Sandbox Code Playgroud)

以下是如何使用该方法的示例:

源范围:-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
Run Code Online (Sandbox Code Playgroud)

如果您需要返回一个整数,只需传入 0 个小数位以确保精度,并将结果转换为 int,如下所示:

int myResult = (int) mapOneRangeToAnother(myNumber, 500, 200, -350, -125, 0);
Run Code Online (Sandbox Code Playgroud)

或者您可以声明该方法返回 int 并删除decimalPrecision参数,然后将最后两行更改为:

int calcScale = (int) Math.pow(10, 0);
return (int) Math.round(finalNumber * calcScale) / calcScale;
Run Code Online (Sandbox Code Playgroud)

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

int myResult = (int) mapOneRangeToAnother(input, input_start, input_end, output_start, output_end, 0);
Run Code Online (Sandbox Code Playgroud)

这是方法:

编辑:正如@CoryGross 所指出的,如果将相同的数字传递到范围的两个端点的方法中from,则会被零除。而且由于该方法应该根据两个数字范围计算一个新数字,如果其中一个范围的端点值相同,则计算结果将毫无意义,因此在这种情况下我们需要返回 null。

这个例子是用Java编写的

public static Double mapOneRangeToAnother(double sourceNumber, double fromA, double fromB, double toA, double toB, int decimalPrecision ) {
    double deltaA = fromB - fromA;
    double deltaB = toB - toA;
    if(deltaA == 0 || deltaB == 0) {  //One set of end-points is not a range, therefore, cannot calculate a meaningful number.
        return null;
    }
    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;
}
Run Code Online (Sandbox Code Playgroud)

在我的用例中,我需要淡出 JavaFX 控件的不透明度,但由于不透明度是从 0 到 1 的数字,我只是使用该方法重新映射范围 1 到 100(基于递增 int 的 for 循环)从 0 到 100)到 0 到 1 的范围,并且效果很好。

虽然我现在知道我可以通过将增量从 1 更改为 0.01 之类的值来创建循环,如下所示:

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

我只是向可能正在做与我所做的类似事情的任何人指出这一点。该方法如所描述的那样完美地工作。

:-)


Qua*_*nic 5

output = ((input - input_start)/(input_end - input_start)) * (output_end - output_start) + output_start
Run Code Online (Sandbox Code Playgroud)

其作用是按比例找出输入在输入范围内“多远”。然后,它将该比例应用于输出范围的大小,以绝对方式找出输出应在输出范围内的距离。然后添加输出范围的起始值以获得实际的输出数。