How come map() function is proper and correct? What is the idea behind it?

Hello, this is my first post on this forum. I am a new arduino user, and through some tutorial I had been guided to use map() function to scale my output from <0,1024> to <1,5>.
Getting weird results I came to the conclusion this function is not working properly OR its use is different than I would thought. I would appreciate if somebody took a closer look to this topic or explained to me why this function is done this way. Here is what I mean:

Having the formula this function uses:
y = (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
for example input <200,1000> and output<2,5> I would want this function to work like this:
If:
xe<200,400) y=2
xe<400,600) y=3
xe<600,800) y=4
xe<800,1000) y=5

This seems to be the ideal way you would like to see mapping function work. For every equal interval of input value you get desired output value. Although, how it really works for these values is:
xe<200,466) y=2
xe<466,732) y=3
xe<732,998) y=4
xe<998,1000) y=5

And now this is really weird. I mean mostly the last output value - if you estimate the input values not perfectly when it comes to the upper bound, you will not be able to acquire last out put value. I am positive that the equation given in the topic linked is really the formula used in the function(I mean, it is not ‘rewrite’ mistake) because this is circa what I got using this function in my program.
What seems to be the problem here, is that this functions acts like for n desired ranges it scales it to n-1.

And now my question is: Is this something deeper than I can actually see and does it make any sense? [quess no]. Why would this be correct? In my opinion it’s just wrong…really wrong.

I wrote myself a function which satisfies my purposes, and it’s a little bit different. However, I have not checked how it works with negative numbers.

y = (x - in_min) * (out_max - out_min +1) / (in_max - in_min) + out_min

Thank you for your reply :slight_smile:

x = 466, y = 2.9975 x = 467, y = 3.00125

x = 732, y = 3.995 x = 733, y = 3.99875 x = 734, y = 4.0025

x = 998, y = 4.9925 x = 999, y = 4.99625

x = 1000, y = 5

Seems like it works just fine to me.

It is not really designed for such a low range of output values.

Why not just divide by 256 (use a shift) and add 1.

Integer math creates truncation errors. If you want higher precision then you can create your own float version, fmap(). Thereafter, you can add 0.5 and cast to an integer for roundoff.

You'll never get a 1024, so if you map it up to 6 instead of 5, your 5 will get the upper portion of 1023 and just below that.

IMO, map is a silly function in general - it provides a functionality that could be replaced by a truly trivial bit of code - I think it's there to help schoolkids control LEDs with potentiometers (because apparently they don't teach kids math anymore)

That moment you realize you can replace

map(x, 0, 1023, 0, 255)

with

x/4

:)

DrAzzy: IMO, map is a silly function in general - it provides a functionality that could be replaced by a truly trivial bit of code - I think it's there to help schoolkids control LEDs with potentiometers (because apparently they don't teach kids math anymore)

:D Funny, and perhaps true, but the same could be said for all the stdlib/math macros like min(), max(), abs(), ceil(), floor(), etc....

However, some use cases are not as simple as an easy calculation such as a simple divide.

A mapping function can be very useful when doing things like drawing a bar graph on a graphic display which can have arbitrary min and max values & ranges, variable bar graphs sizes and arbitrary coordinate locations. A mapping function can really simplify creating all the coordinates needed and is pretty much the only way to handle it.

For the arduino map() function, Arduino want to consider using multiple overloaded functions, a C++ template, or implement it as a smarter gcc cpp macro using typeof() to ensure that the argument types are used for the actual calculation so that floats are not truncated down to integers. i.e. auto detect the types and use that type for the calculation and return value.

--- bill

INTP: That moment you realize you can replace

map(x, 0, 1023, 0, 255)

with

x/4

:)

And then the compiler replaces that with a bit-shift operation that's pretty much instantaneous. (The division operator is actually quite slow. Dividing by 3 would take many times longer because it isn't a simple bit shift.)

INTP: That moment you realize you can replace

map(x, 0, 1023, 0, 255)

with

x/4

:)

. . . you fail the interview. The two are not equivalent.

gawroon7:
Having the formula this function uses:
y = (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
for example input <200,1000> and output<2,5> I would want this function to work like this:
If:
xe<200,400) y=2
xe<400,600) y=3
xe<600,800) y=4
xe<800,1000) y=5

The map() function was written with integers (why, I don’t know).

What I did on my end was change the code to use floating point:

double map (double x, double x1, double x2, double y1, double y2)
{
    return (x - x1) * (y2 - y1) / (x2 - x1) + y1;
}

By the way, all it’s doing is calculating a first order linear fit (i.e. Y = MX + B).

Maybe this will help... http://forum.arduino.cc/index.php?topic=46546.0

MorganS:
And then the compiler replaces that with a bit-shift operation that’s pretty much instantaneous. (The division operator is actually quite slow. Dividing by 3 would take many times longer because it isn’t a simple bit shift.)

And fail the interview again, x/4 is not the same as x>>2 (actually the latter is more correct
for most purposes due to C’s broken handling of the modulo operator when negative integers are involved)

If the input is constrained to 0-1023, such as an analogRead() then it's a good approximation. If you're mapping negative numbers with that expression, then the approximation is a fail.

MarkT: And fail the interview again, x/4 is not the same as x>>2 (actually the latter is more correct for most purposes due to C's broken handling of the modulo operator when negative integers are involved)

That depends on the interviewers question. If he mentioned the word unsigned and you followed with that line of thinking then it would be your fail not for coding but for lack of listening ability. In terms of scaling an analogRead 10 bit value down to 8 bits for a analogWrite on an Arduino I would expect you to know it was unsigned without being told.

Good enough for 'schoolkids controlling LEDs with potentiometers', which was the premise. No bonus points for being computer code pedantic to a 3rd grader.

INTP: Good enough for 'schoolkids controlling LEDs with potentiometers', which was the premise.

The original poster makes no mention of LEDs or potentiometers. An F for reading comprehension.

[quote author=Coding Badly link=msg=2876765 date=1470855880] The original poster makes no mention of LEDs or potentiometers. An F for reading comprehension.

[/quote]

My fault for not quoting the previous poster I was directly responding to to make it obvious.

Got some eager beaver F givers around here :o

AWOL:
. . . you fail the interview.
The two are not equivalent.

While that is true, I would argue for the ranges given, and assuming x is 0 to 1023, that they should be the same.
i.e.
val/4 should equal map(val, 0,1023, 0, 255)
when val is a value from 0 to 1023 inclusive.

However, even with those restrictions they will not produce the same results.
The reason is that the code implementation of map() does not properly map the values from one range to another.
Which probably explains some of the OPs questions/confusion.
There are some “off by one” issues in the map() function calculation.

i.e. if you have a range of 0 to 1023 there are 1024 possible mapping points not 1023.
The calculation in map() fails to take that into consideration.
As a result it does not properly map the source value into to the target range.

For example, if you were mapping numbers 1 to 10 to be numbers 1 to 5 you would expect that each two consecutive numbers from 1 to 10 would map to a number from 1 to 5; however when using map() they don’t.

The following sketch demonstrates the issue with smaller mapping ranges 1-10 and 1-5
It shows the mapping of the value using the Arduino map() function and a modified version, map2()

long map2(long x, long in_min, long in_max, long out_min, long out_max)
{
        return (x - in_min) * (out_max - out_min+1) / (in_max - in_min+1) + out_min;
}


void setup(void)
{
long val, mval, mval2;

	Serial.begin(9600);
	while(!Serial){}

	Serial.println("Mapping numbers from 1 to 10 to 1 to 5");
	Serial.println("Using map(), and altered map2() function");
	for(val = 1; val <= 10; val++)
	{
		mval = map(val,1,10,1,5);
		mval2 = map2(val,1,10,1,5);

		Serial.print("map(");
		if(val < 10)
			Serial.print(' ');
		Serial.print(val);
		Serial.print(",1,10,1,5) value: ");
		Serial.print(mval);

		Serial.print(" map2(");
		if(val < 10)
			Serial.print(' ');
		Serial.print(val);
		Serial.print(",1,10,1,5) value: ");
		Serial.print(mval2);
		Serial.println();
	}
}
void loop(void){}

And here is the output:

Mapping numbers from 1 to 10 to 1 to 5
Using map(), and altered map2() function
map( 1,1,10,1,5) value: 1 map2( 1,1,10,1,5) value: 1
map( 2,1,10,1,5) value: 1 map2( 2,1,10,1,5) value: 1
map( 3,1,10,1,5) value: 1 map2( 3,1,10,1,5) value: 2
map( 4,1,10,1,5) value: 2 map2( 4,1,10,1,5) value: 2
map( 5,1,10,1,5) value: 2 map2( 5,1,10,1,5) value: 3
map( 6,1,10,1,5) value: 3 map2( 6,1,10,1,5) value: 3
map( 7,1,10,1,5) value: 3 map2( 7,1,10,1,5) value: 4
map( 8,1,10,1,5) value: 4 map2( 8,1,10,1,5) value: 4
map( 9,1,10,1,5) value: 4 map2( 9,1,10,1,5) value: 5
map(10,1,10,1,5) value: 5 map2(10,1,10,1,5) value: 5

Notice that the current map() function is not properly doing the mapping.
In my world of math, map(3,1,10,1,5) should return 2 not 1 which is what the modified map2() function returns.

The corrected map() function may be more complicated than simply always adding one to the range size.

I’ve not gone through the negative values and inverse range scenarios but it may get more complicated when taking that into consideration so the corrected map() function may be more complicated than simply always adding one to the range size.

But clearly the current map function is wrong, in that it is using the incorrect total number of points in the ranges to calculate the percentage ratio into the target range.

— bill

While that is true, I would argue for the ranges given, and assuming x is 0 to 1023, that they should be the same. i.e. val/4 should equal map(val, 0,1023, 0, 255) when val is a value from 0 to 1023 inclusive.

Argue and assume all you like, but map (1020, 0, 255, 0, 1023) for example, does not give the same value as 1020 / 4.

(map (1020, 0, 256, 0, 1024) does)

Edited: That'll teach me to proofread