Thanks. Yes, map() has been brought up quite a few times, but none (at least, none that I have seen) address map()'s loss-of-precision.
As a trivial example, say you need to map the range [0,7] to [0,1] (there are better ways of mapping these two particular ranges, but I'm using them here as a simple example). map(), as implemented, gives two different outputs depending on the order of the argument values supplied.
According to map()'s documentation, it shouldn't matter, ie, map(x,0,7,1,0) should simply reverse the mapping that map(x,0,7,0,1) produces, but it doesn't, and it doesn't because map() does an 'implicit division' (see attached analysis in OP). This isn't addressed in any of the posts that I have seen.
Here is the output map() produces for all four arrangements of the above input values, ie, where
in_min < in_max, out_min < out_max
in_min > in_max, out_min < out_max
in_min < in_max, out_min > out_max
in_min > in_max, out_min > out_max
Testing map(x,0,7,0,1) over interval x=[0,7]
(asterisk (*) marks values of x within specified bounds)
x: 0, y: 0 *
x: 1, y: 0 *
x: 2, y: 0 *
x: 3, y: 0 *
x: 4, y: 0 *
x: 5, y: 0 *
x: 6, y: 0 *
x: 7, y: 1 *
Testing map(x,7,0,0,1) over interval x=[0,7]
(asterisk (*) marks values of x within specified bounds)
x: 0, y: 1 *
x: 1, y: 0 *
x: 2, y: 0 *
x: 3, y: 0 *
x: 4, y: 0 *
x: 5, y: 0 *
x: 6, y: 0 *
x: 7, y: 0 *
Testing map(x,0,7,1,0) over interval x=[0,7]
(asterisk (*) marks values of x within specified bounds)
x: 0, y: 1 *
x: 1, y: 1 *
x: 2, y: 1 *
x: 3, y: 1 *
x: 4, y: 1 *
x: 5, y: 1 *
x: 6, y: 1 *
x: 7, y: 0 *
Testing map(x,7,0,1,0) over interval x=[0,7]
(asterisk (*) marks values of x within specified bounds)
x: 0, y: 0 *
x: 1, y: 1 *
x: 2, y: 1 *
x: 3, y: 1 *
x: 4, y: 1 *
x: 5, y: 1 *
x: 6, y: 1 *
x: 7, y: 1 *
Note the difference? map()'s output is mostly ones when out_min and out_max are reversed, but mostly zeros otherwise. Even more noteworthy is that (theoretically, anyway) the output of the first and the last tests should be identical; reversing the order of one pair or reversing the order of the other pair should reverse the mapping, but when you reverse both pair you are effectively reversing the mapping twice. In other words, you should get the same results as if you hadn't reversed either one.
map2() preserves precision for integer division and so does not have this sensitivity to input-value order:
Testing map2(x,0,7,0,1) over interval x=[0,7]
(asterisk (*) marks values of x within specified bounds)
x: 0, y: 0 *
x: 1, y: 0 *
x: 2, y: 0 *
x: 3, y: 0 *
x: 4, y: 0 *
x: 5, y: 0 *
x: 6, y: 0 *
x: 7, y: 1 *
Testing map2(x,7,0,0,1) over interval x=[0,7]
(asterisk (*) marks values of x within specified bounds)
x: 0, y: 1 *
x: 1, y: 0 *
x: 2, y: 0 *
x: 3, y: 0 *
x: 4, y: 0 *
x: 5, y: 0 *
x: 6, y: 0 *
x: 7, y: 0 *
Testing map2(x,0,7,1,0) over interval x=[0,7]
(asterisk (*) marks values of x within specified bounds)
x: 0, y: 1 *
x: 1, y: 0 *
x: 2, y: 0 *
x: 3, y: 0 *
x: 4, y: 0 *
x: 5, y: 0 *
x: 6, y: 0 *
x: 7, y: 0 *
Testing map2(x,7,0,1,0) over interval x=[0,7]
(asterisk (*) marks values of x within specified bounds)
x: 0, y: 0 *
x: 1, y: 0 *
x: 2, y: 0 *
x: 3, y: 0 *
x: 4, y: 0 *
x: 5, y: 0 *
x: 6, y: 0 *
x: 7, y: 1 *
Another question about map() that was brought up in several posts concerns how it doesn't uniformly map an input range to an output range. Various ad hoc fixes were proposed to address this, none of them with much success. The closest one addresses only those cases where the slope is positive, but gives spurious results when it is negative. Ideally, the mapping should be insensitive to value order apart from the reversing the mapping as appropriate.
I've written a mapping function that addresses this concern, one that maps intervals. I'll write more about it in a later post but, briefly, this is how it uniformly maps the two intevals mentioned above.
This new function is called mapIntervals():
Testing mapIntervals(x,0,7,0,1) over interval x=[0,7]
(asterisk (*) marks values of x within specified bounds)
x: 0, y: 0 *
x: 1, y: 0 *
x: 2, y: 0 *
x: 3, y: 0 *
x: 4, y: 1 *
x: 5, y: 1 *
x: 6, y: 1 *
x: 7, y: 1 *
Testing mapIntervals(x,7,0,0,1) over interval x=[0,7]
(asterisk (*) marks values of x within specified bounds)
x: 0, y: 1 *
x: 1, y: 1 *
x: 2, y: 1 *
x: 3, y: 1 *
x: 4, y: 0 *
x: 5, y: 0 *
x: 6, y: 0 *
x: 7, y: 0 *
Testing mapIntervals(x,0,7,1,0) over interval x=[0,7]
(asterisk (*) marks values of x within specified bounds)
x: 0, y: 1 *
x: 1, y: 1 *
x: 2, y: 1 *
x: 3, y: 1 *
x: 4, y: 0 *
x: 5, y: 0 *
x: 6, y: 0 *
x: 7, y: 0 *
Testing mapIntervals(x,7,0,1,0) over interval x=[0,7]
(asterisk (*) marks values of x within specified bounds)
x: 0, y: 0 *
x: 1, y: 0 *
x: 2, y: 0 *
x: 3, y: 0 *
x: 4, y: 1 *
x: 5, y: 1 *
x: 6, y: 1 *
x: 7, y: 1 *
mapIntervals() is especially useful in applications where you're mapping say, for example, a potentiometer shaft-angle to a 10-LED bargraph display. Your application splits the total rotation angle (most pots are typically 270 degrees stop-to-stop) into ten sectors of 27 degrees each. Using map() will not work because it will light the tenth LED only when the analog value reaches 1023 (worse, noise on the analog pin may cause the display to flicker between LED 9 and LED 10). Not what you want. mapIntervals() maps the last 27-degree sector the same as it maps the other nine, ie, any angle within the last 27 degrees will light LED 10 just as the previous 27 lit LED 9.
For example: led_num = mapIntervals(analog_val,0,1023,1,10) maps the intervals:
analog_val led_num vals/interval
0 thru 102 1 103
103 thru 204 2 102
205 thru 307 3 103
308 thru 409 4 102
410 thru 511 5 102
512 thru 614 6 103
615 thru 716 7 102
717 thru 819 8 103
820 thru 921 9 102
922 thru 1023 10 102
-----
1024
I'll post more about mapIntervals() later along with its theoretical treatment.
-PW