将数值范围映射到另一个

时间:2011-04-20 14:21:34

标签: 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;

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

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

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

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

6 个答案:

答案 0 :(得分:90)

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

首先,如果我们要将[0x]范围内的输入数字映射到输出范围[0y],我们只需要进行扩展适量。 0变为0,x变为yt变为(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。然后,我们可以通过添加youtput_startoutput = 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))

答案 1 :(得分:21)

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

答案 2 :(得分: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;

答案 3 :(得分:10)

公式是

  

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,首先让它标准化,使0为input_end,1为input_start。这是使问题更容易的简单技术。

我们该怎么做?我们必须将input_start数量留下的所有内容都移位,这样如果输入x恰好为f(x),则它应该为零。

所以,让我们说f(x) = x - input_start 是进行转换的函数。

f(input_start) = input_start - input_start = 0

让我们试一试:

input_start

适用于input_end

此时,它不适用于f(x) = (x - input_start) / (input_end - input_start) ,因为我们还没有缩放它。

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

input_end

好的,让我们试试(input_end) = (input_end - input_start) / (input_end - input_start) = 1

˚Ff(x) = (x - input_start) / (input_end - input_start) * (output_end - output_start)

很棒,似乎很有效。

好的,下一步,我们实际上将它缩放到输出范围。只需乘以输出范围的实际长度即可:

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

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

反过来最终将成为:

#sx {
  -webkit-clip-path: polygon(0 0, 95% 0, 74% 100%, 0% 100%);
  clip-path: polygon(0 0, 100% 0, 84% 100%, 0% 100%);
  background-image: url('https://unsplash.it/1920/1080/?random');
  //background-image: url('http://loremflickr.com/1920/1080/dog,cat');
  background-position: center center;
  background-clip: border-box;
  background-repeat: no-repeat;
  background-size: cover !important;
  background-attachment: scroll;
  overflow: scroll;
  width: 50vw !important;
  height: 100vh;
  display: flex;
  align-items: center;
  align-content: center;
  flex-direction: column;
  transition: all 0.5s cubic-bezier(.34, -0.32, .42, 1.34);
  &:hover {
    border: 10px solid white;
    width: 100vw !important;
    -webkit-clip-path: polygon(0 0, 100% 0, 100% 100%, 0% 100%);
    clip-path: polygon(0 0, 100% 0, 100% 100%, 0% 100%);
    ~ #dx {
      //width:100vw !important;
    }
    h1 {
      //margin-left: 25vw;
      color: white;
      @include textlongshadow(#222);
    }
    img {
      //margin-left: 25vw;

      @include longshadow();

      //-webkit-filter:drop-shadow(10px 10px 10px white) drop-shadow(20px 20px 10px red);
      //longdrop(20,#222);
    }
  }

如您所见,它现在似乎已正确映射。

答案 4 :(得分:2)

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

这样做可以按比例找出输入范围“输入范围有多远”。然后将该比例应用于输出范围的大小,以绝对值计算输出应该在输出范围内的距离。然后它添加输出范围的开始以获得实际输出数。

答案 5 :(得分:2)

这保证将ANY范围映射到ANY范围

我写了这种方法,它精确地遵循了将一个数字范围映射到另一个数字范围的代数公式。计算是通过使用double进行的,以保持精度,然后最后,它将返回一个Double,该double具有您在方法参数中指定的许多小数位。

该函数的工作方式如下...注意,我标记了范围A和B的末端-这是因为没有必要使范围从低端开始并从高端结束,诸如此类...您可以使第一个范围300到-7000,第二个范围-3500到5500 ...为范围选择的数字没有区别,它将正确地映射它们。

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

如果您需要返回整数,只需告诉它返回结果(保留小数点后1位),然后将结果转换为int即可:

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

在OP最初的问题中,他们将使用如下功能:

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

这是函数:

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到100的数字来获取0到1之间的所有分数...这是一种更干净的方法,因为使用类似这样的函数,主代码看起来比实际的效果更好快速进行数学运算。

您的里程可能有所不同,请尽情享受! :-)