Arduino’s map() Function and Numeric Distribution

The Arduino map() function is an interesting beast. Very technically it works exactly as its documented to work, but not the way almost every example uses it.

Here’s an example you can find in hundreds of sketches online, including the actual documentation for map():

val = map(val, 0, 1023, 0, 255);

This is a simple map, and one would expect that every four ticks on the input would map to one tick on the output (that is, {0,1,2,3} -> 0, {4,5,6,7} -> 1, etc). But that’s not what the function above actually does.

To show the issue, let’s make the output range smaller (but still an even divisor):

val = map(val, 0, 1023, 0, 15);

This should result in an even distribution, 64 input ticks per one output tick. To test this, I wrote a quick script implementing the Arduino map logic (which they were nice enough to document):

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

The script prints a table of output values and the number of times that value was returned. Here’s the output for map(?, 0, 1023, 0, 15) for each value in 0..1023:

map(0..1023, 0, 1023, 0, 15);
  0   69
  1   68
  2   68
  3   68
  4   68
  5   69
  6   68
  7   68
  8   68
  9   68
 10   69
 11   68
 12   68
 13   68
 14   68
 15    1

That’s definitely not an even distribution. Now here’s a really stark example:

map(0..1023, 0, 1023, 0, 1);
  0 1023
  1    1

That’s a pretty egregious imbalance.

I mentioned earlier that the function’s actually working how it’s documented to work, just not how it’s usually used in examples. The map() docs state that “[t]he map() function uses integer math so will not generate fractions, when the math might indicate that it should do so. Fractional remainders are truncated, and are not rounded or averaged.”

This completely makes sense – if you imagine a range of 1024 values between 0 and one, all of them will be less than 1 except the last value, and since it’s integer arithmetic, all the less-than-1 values are 0.

The solution is fairly simple – increase the in_max and out_max args by one more than the actual maximum value (and then wrap the output in constrain(), which you ought to have done anyway). It’s fairly easy to work through why this works in your head, but here are the same examples I gave above with the increased maximums:

map(0..1023, 0, 1024, 0, 2);
  0   512
  1   512
map(0..1023, 0, 1024, 0, 16);
  0   64
  1   64
  2   64
  3   64
  4   64
  5   64
  6   64
  7   64
  8   64
  9   64
 10   64
 11   64
 12   64
 13   64
 14   64
 15   64

I have worked through this and I now understand how to get the values I want out of constrain. What I don’t know, however, is why the docs and the examples don’t address this issue. I know I’m not the first person to find this issue because I’ve found sketches on the internet where people are doing this (and they’re doing it in a way that leads me to believe they’re doing it on purpose), but I couldn’t get Google to show me any pages where someone actually addresses this point. I wonder why?

As for why I was looking into this, I was playing around with a couple of pots and an LCD, making a fakey etch-a-sketch using the 2-row LCD I have for such things. I could not figure out why my vertical control would only drop me down a line when I had the pot turned 100%. Now I know…

Edit 2011-09-13:

I posted this in the arduino.cc forum last night. As I expected, this is an issue known in the arduino community. Among other things, I was referenced to this post discussing the issue. My problem with all of this isn’t so much that it’s odd behavior, but that it’s been odd for at least 2 years and there’s no mention of the oddness in the map() documentation. The docs are a wiki, so I proposed an addition in the forum thread, maybe I’ll get to add it and help someone out in the future.

6 thoughts on “Arduino’s map() Function and Numeric Distribution

  1. I was testing this out in C and what you are saying holds weight. However, like you said, the Arduino documentation they say exactly what they are doing. Notice that there are long variables passed and not int. In order for it to round properly, use map(x, 0.0, 1023.0, 0.0, 255.0)

  2. I want to use this float value… i have analog inputs from 0-1024 but i want mapped it into 40.0, 40.1, 40.2, 40.3, ……. 59.8, 59.9, 60

    i am using

    int freq = analogRead(F_sensor);
    float Freq = map(freq, 0, 1023, 40, 60);

    but not getting anwer…

    please help..
    Thanks & Regards,
    Priyam Sharma

Leave a Reply

Your email address will not be published. Required fields are marked *