playing with (out of) ranges to improve map() e.g. mapping to percentage

// described here as I cannot edit the MAP() reference page

I recently had to map analogRead() to a percentage. Easy one would say, use the map function.

x = analogRead(A0);  // x = 0..1023
percent = map(x, 0, 1023, 0, 100);  // make percent of it

As the integer math truncates there will be on average about 10.2 input values mapped on every output value
EXCEPT for output value 100, as only the max input value 1023 is mapped upon the max output value 100

0 0
...
1013 99 
...
1022 99 
1023 100

This mapping was not good as I wanted to have at least 10 values mapped upon 100, just like the other values.

There are 2 solutions, both tweaking the ranges .

x = analogRead(A0);  // x = 0..1023
percent = map(x, 0, 1013, 0, 100);  // make percent of it

This only changes one input value, but at first sight it might be not clear why it should be 1013.
// 1013 = 1023 - 10, where 10 is # occurrences I want to have the max value of 100; (in fact this is not even true!, see output sketch below)
// note it can't be 1012 or 1011 that will give trouble

So in short the above works - sort of - but can be tricky to understand. Especially next month or so when you review your code :wink:

The other (better) way is

x = analogRead(A0);  // x = 0..1023
percent = map(x, 0, 1024, 0, 101);  // make percent of it

This changes both the input range and the output range by 1.
This works as we saw that the max output value only occurs for the max input value of the map(). As the max input value is one higher that the max value analogRead can provide it will never be reached. Thus the max output value will also never be reached.

A third way to use float math and do proper rounding would work to but is a performance killer.

a small sketch shows the differences

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

  for (int i = 0; i < 1024; i++)
  {
    int x = map(i, 0, 1023, 0, 100);
    int y = map(i, 0, 1013, 0, 100);
    int z = map(i, 0, 1024, 0, 101);
    Serial.print(i);
    Serial.print("\t");
    Serial.print(x);
    Serial.print("\t");
    Serial.print(y);
    Serial.print("\t");
    Serial.println(z);
  }
}

void loop() {}

to be continued ...

Yea, you could use the map function for this, but for something as simple as a percentage, the overhead is a bit more than needed.

This is all you really need.
percent = (analogRead(A0) / 1023) * 100; // 888 bytes(int)
percent = (analogRead(A0) / 1023.0) * 100.0; // 1516 (float)

percent = map(analogRead(A0), 0, 1023, 0, 100); // 1214 bytes
percent = map(analogRead(A0), 0, 1023.0, 0, 100.0); // 1346 bytes

I don't have an arduino here to test the precision, but I'm pretty sure the map functions will return the same results.

HazardsMind:
Yea, you could use the map function for this, but for something as simple as a percentage, the overhead is a bit more than needed.

This is all you really need.
percent = (analogRead(A0) / 1023) * 100; // 888 bytes(int)
....
I don't have an arduino here to test the precision, but I'm pretty sure the map functions will return the same results.

Sorry not that trivial.
the example will fail as analogRead()/1023 will allways be 0 (=> %) unless it returns 1023 (=> 100%),

changing the order of the math brings some relief - for lower 1/3th -

percent = 100 * analogRead(A0) / 1023;
doing the multiply first will give an out of range value for int

percent = 100L * analogRead(A0) / 1023; // cast to long first does the job (the map function does that too)
or
percent = 25 * analogRead(A0) / 255; // make use of truncating division effects.

in float I would use
percent = analogRead(A0) / 10.230 ;
or better
percent = analogRead(A0) * 0.097751711 ; / / mul faster than div

That said, the above post was mostly to show that one can explicitly use the out of range values in the map function to get a more appropriate result.

Ok, that's understandable, and an error on my part. Decimal points don't do so well as int types.