PaulStoffregen
Well-known member
Arduino's map() function has always left quite a bit to be desired. They've had a long standing bug report about the issues, and of course numerous times people have discussed using floating point.
Well, I decided it's time to put a better map() function into Teensyduino. At least I hope it's better....
For a little background, here's a simple test program:
This is supposed to map 0-7 onto 0-3. But here's what you actually get:
So the first big problem is integer truncation is handled poorly.
The other big problem is not support at all for floats. If the variable you're trying to map to a new range is a float, it gets converted to an integer and then put through this poor integer math.
After much experimenting with C++ function overloading, I finally ended up with a C++ template solution using C++11 type trairs and SFINAE. The template syntax looks horribly complex.
https://github.com/PaulStoffregen/cores/commit/68aecd945adbc94a786c2499ed58d2e438233f12
With this approach we can have st42's proposed improvement to the integer algorithm, which is:
and when the input number is a float or double, the original math gets computed as 32 or 64 bit floating point.
The integer case becomes:
Now float can finally be used for map too:
This change is scheduled to become part of Teensyduino 1.37.... unless you talk me out of it.
Or maybe there's some way the integer round off could be handled even better? Is st42's way really the best? It seems better than all the other alternatives, but it does round towards zero and gives more values at zero when the target range spans negative to positive.
Well, I decided it's time to put a better map() function into Teensyduino. At least I hope it's better....
For a little background, here's a simple test program:
Code:
void setup()
{
while (!Serial) ;
Serial.println("map function test");
long in_min = 0;
long in_max = 7;
long out_min = 0;
long out_max = 3;
for (int i=in_min-5; i <= in_max+5; i++) {
Serial.print(i);
Serial.print(" --> ");
Serial.println(map(i, in_min, in_max, out_min, out_max));
}
}
void loop()
{
}
This is supposed to map 0-7 onto 0-3. But here's what you actually get:
map function test
-5 --> -2
-4 --> -1
-3 --> -1
-2 --> 0
-1 --> 0
0 --> 0
1 --> 0
2 --> 0
3 --> 1
4 --> 1
5 --> 2
6 --> 2
7 --> 3
8 --> 3
9 --> 3
10 --> 4
11 --> 4
12 --> 5
So the first big problem is integer truncation is handled poorly.
The other big problem is not support at all for floats. If the variable you're trying to map to a new range is a float, it gets converted to an integer and then put through this poor integer math.
After much experimenting with C++ function overloading, I finally ended up with a C++ template solution using C++11 type trairs and SFINAE. The template syntax looks horribly complex.
https://github.com/PaulStoffregen/cores/commit/68aecd945adbc94a786c2499ed58d2e438233f12
With this approach we can have st42's proposed improvement to the integer algorithm, which is:
Code:
if ((in_max - in_min) > (out_max - out_min)) {
return (x - in_min) * (out_max - out_min+1) / (in_max - in_min+1) + out_min;
} else {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
and when the input number is a float or double, the original math gets computed as 32 or 64 bit floating point.
The integer case becomes:
map function test
-5 --> -2
-4 --> -2
-3 --> -1
-2 --> -1
-1 --> 0
0 --> 0
1 --> 0
2 --> 1
3 --> 1
4 --> 2
5 --> 2
6 --> 3
7 --> 3
8 --> 4
9 --> 4
10 --> 5
11 --> 5
12 --> 6
Now float can finally be used for map too:
map function test
-5 --> -2.14
-4 --> -1.71
-3 --> -1.29
-2 --> -0.86
-1 --> -0.43
0 --> 0.00
1 --> 0.43
2 --> 0.86
3 --> 1.29
4 --> 1.71
5 --> 2.14
6 --> 2.57
7 --> 3.00
8 --> 3.43
9 --> 3.86
10 --> 4.29
11 --> 4.71
12 --> 5.14
This change is scheduled to become part of Teensyduino 1.37.... unless you talk me out of it.
Or maybe there's some way the integer round off could be handled even better? Is st42's way really the best? It seems better than all the other alternatives, but it does round towards zero and gives more values at zero when the target range spans negative to positive.