multiMap() function - integer linear interpolation

Sorry - code and example snippets can be found on the playground - http://arduino.cc/playground/Main/MultiMap

Rob
[update 30-1-2011]

  • didn't post the code here as small optimizations are planned
    [update 2-2-2011]
  • code on playground updated.

[update 23-03-2011]

  • added float version

I know this is an old thread, but I made it work with decremented IN values:

int multiMap(int val, int* _in, int* _out, uint8_t size)
{
uint8_t pos = 1;
if(_in[0] < _in[1]) {
if (val <= _in[0]) return _out[0];
if (val >= _in[size-1]) return _out[size-1];
if (val == _in[pos]) return _out[pos];
while(val > _in[pos]) pos++;
} else {
if (val >= _in[0]) return _out[0];
if (val <= _in[size-1]) return _out[size-1];
if (val == _in[pos]) return _out[pos];
while(val < _in[pos]) pos++;
}
// interpolate in the right segment for the rest
return map(val, _in[pos-1], _in[pos], _out[pos-1], _out[pos]);
}

If you have the option of using both ints and floats, why not use one function and use a template?

HazardsMind:
If you have the option of using both ints and floats, why not use one function and use a template?

would be better...

jmcgihon:
I know this is an old thread, but I made it work with decremented IN values:
...

Thanks, can be really valuable in some cases (e.g. the IN array is measured runtime)
unfortunately it doubles the footprint,

robtillaart:
would be better...

put this in a file multiMap.h and use it as a library

#include "Arduino.h"

template<typename T>
T multiMap(T val, T* _in, T* _out, uint8_t size)
{
  // take care the value is within range
  // val = constrain(val, _in[0], _in[size-1]);
  if (val <= _in[0]) return _out[0];
  if (val >= _in[size-1]) return _out[size-1];

  // search right interval
  uint8_t pos = 1;  // _in[0] allready tested
  while(val > _in[pos]) pos++;

  // this will handle all exact "points" in the _in array
  if (val == _in[pos]) return _out[pos];

  // interpolate in the right segment for the rest
  return (val - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]) + _out[pos-1];
}

demo sketch

#include "multiMap.h"

int in[] = { 
  11,22,33};
int out[] = {
  111,222,555};

float fin[] = { 
  11,22,33};
float fout[] = {
  111,222,555};

uint32_t start;
uint32_t stop;

void setup() 
{
  Serial.begin(115200);
  Serial.println("Start ");

  start = micros();
  float  x = multiMap<int>(12, in, out, 3);  
  stop = micros();
  Serial.println(stop-start);  
  Serial.println(x);

  start = micros();
  float  y = multiMap<float>(12, fin, fout, 3);
  stop = micros();
  Serial.println(stop-start);
  Serial.println(y);
}

void loop() {}

disclaimer, not tested extensively.

updated the playground to have a reference

Thanks for the library Rob.

If you recall my old post about adding new core functions, I made a new map function with the aid of Pyro and useful criticism of Mark and Robin.

I never posted it, but here is my modified map version.

template< typename A, typename B, typename C, typename D, typename E > 
auto map( const A &x, B &&in_min, C &&in_max, D &&out_min, E &&out_max ) -> decltype( A() * B() * C() * D() * E() ) 
{ 
  typedef decltype( A() * B() * C() * D() * E() ) T_out;
  //((x < in_min)? in_min : ((x > in_max)? in_max : x)) -> constrain without overhead
  return (T_out)( ((x < in_min)? in_min : ((x > in_max)? in_max : x)) - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

I never posted it, but here is my modified map version.

Does it really make sense to be able to specify any type for each of the 5 arguments?

How would that deal with this:

String valueToMap = "Hello world";
int fromMin = 0;
float fromMax =14.7;
char toMin = 'G';
char *toMax = "The answer to life, the universe and everything";

godOnlyKnowsWhatKind mappedValue = map(valueToMap, fromMin, fromMax, toMin, toMax);

I never noticed it won't take values assigned to variables and it doesn't like Strings or strings, but everything else works. However I reverted it back to just this.

template< typename A, typename B, typename C, typename D, typename E > 
auto map( const A x, B in_min, C in_max, D out_min, E out_max ) -> decltype( x * in_min * in_max * out_min * out_max ) 
{ 
  typedef decltype( x * in_min * in_max * out_min * out_max) T_out;
  //((x < in_min)? in_min : ((x > in_max)? in_max : x)) -> constrain without overhead
  return (T_out)( ((x < in_min)? in_min : ((x > in_max)? in_max : x)) - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

So no Strings and/or strings and were good to go.

Thanks Paul

Even disallowing Strings and strings, does it make sense to use different types for the from range (float fromMin, long fromMax), for instance? Does it make sense to use different types for the to range (long long toMin, byte toMax)?

C allows you to shoot yourself in the foot. C++'s templates allow you to blow your whole leg off.

This is a situation, I think, where a template is NOT a good idea.

Thanks for sharing!

I think it is a great example what can be done with templates, but I agree with PaulS that using different types for all parameters is overkill for 99.9% of all sketches I have seen.

If a user wants performance (s)he inlines the formula
If a user wants precision (s)he either selects float (because it rounds better in the division) or uses long.

The whole point to allowing multiple types was for decltype to choose the largest type and make it the returning result.

Originally I only allowed one type, but then you lose precision if the first type (valueToMap) is smaller then the rest. So allowing multiple types solves that, but as Paul pointed out, it can also cause problems if the data entered is foolish. A possible scenario for new programmers, i'm sure, but we can't prevent everything.

but as Paul pointed out, it can also cause problems

I wasn't picking on your efforts. I've never had the need for templates, so I've not studied up on them. I don't know if you can create a template that takes different types, and still require that some of the function arguments must all be the same type. I guess it's time to get over the lack of knowledge of templates.

I wasn't picking on your efforts.

I wasn't saying you were, its just helpful criticism for me and others who read these posts.

PaulS:
Even disallowing Strings and strings, does it make sense to use different types for the from range (float fromMin, long fromMax), for instance? Does it make sense to use different types for the to range (long long toMin, byte toMax)?

C allows you to shoot yourself in the foot. C++'s templates allow you to blow your whole leg off.

This is a situation, I think, where a template is NOT a good idea.

Silly use of any code is a bad idea, and yes it does make sense if you understand the declaration.

You can read the post where I created this function (apart from the single map algo line that hazardsmind changed) and you will see why the different template parameters matter. Some new "ideas for" core functions - #27 by pYro_65 - Libraries - Arduino Forum

And there is no reason why you cannot expand on type safety allowing only the types that you want. There is always a bigger picture :slight_smile:

How do I set up a library for multiMap
I have a error multiMap was not declared in this scope

Hello robtillaart,

I was wondering if you ever got round to reviewing the to-do list on Arduino Playground?

  • Check if binary search is faster
  • Implement a simple cache that holds the last used value (some projects would benefit)

I'll be grateful for any suggestions to optimise multimap to make it any faster if possible?

Paul

Hi,
I've been trying to get multiMap to work, but it seems to be all over the place for me.
Code I'm using is below. I've simplified it down, and set the map data so input and output is the same, and it still doesn't output consistently. What am I doing wrong?
Thanks

#include <LiquidCrystal.h>

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

int outputreading = 0;
// note: the _in array should have increasing values
int multiMap(int val, int* _in, int* _out, uint8_t size)
{
// take care the value is within range
// val = constrain(val, _in[0], _in[size-1]);
if (val <= _in[0]) return _out[0];
if (val >= _in[size-1]) return _out[size-1];

// search right interval
uint8_t pos = 1; // _in[0] allready tested
while(val > _in[pos]) pos++;

// this will handle all exact "points" in the _in array
if (val == _in[pos]) return _out[pos];

// interpolate in the right segment for the rest
return (val - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]) + _out[pos-1];
}

void setup() {
Serial.begin(9600);
lcd.begin(16, 2);
}

void loop() {
lcd.setCursor(0, 0);
int out[] = {0,205,409,615,819,1021};
int in[] = {0,205,409,615,819,1021};
int val = analogRead(A0);
outputreading = multiMap(val, in, out, 6);
lcd.print(outputreading);
}