Unexpected results from map function

I’m trying to use the map function to change values that range from 10000000-15000000 to 0-65535 but I’m getting all kinds of unexpected results. The prototype for that function is

long map(long x, long in_min, long in_max, long out_min, long out_max)

so it should be able to handle values that are ~ +/- 2 billion-ish. To test I tried the following code

for(long a = 5000000; a < 10000000; a += 500000) {
  Serial.println("a: " + String(a) + " -> " + String(map(a, 5000000, 10000000, 0, 65535)));
  delay(500);
}

and got this as output

a: 5000000 -> 0
a: 5500000 -> -318
a: 6000000 -> 222
a: 6500000 -> -96
a: 7000000 -> -414
a: 7500000 -> 125
a: 8000000 -> -192
a: 8500000 -> 347
a: 9000000 -> 29
a: 9500000 -> -289

Despite the prototype is this really only for small ranges?

The board that I’m using is the Leonardo, I’m not sure but that may make a difference if it treats a long as something other than a 32 bit integer like the documentation suggests then I imagine it will.

Thanks.

Ditch the String class and try again. Until you do that, you can't be sure where the problem lies.

This is the implementation of the max function (found in WMath.cpp):

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

Given x = 5500000 and your other arguments, the product (x - in_min) * (out_max - out_min) yields 500,000 x 65,535 = 32,768,000,000, which is far outside a 32-bit long's value range and overflows even before the division is done.

Michael, thanks for pointing that out. I could have sworn I ran a couple of numbers through a calculator but I was either out to lunch or didn't use my actual values. I ended up just writing my own single-purpose map as

unsigned int _map(long x) {
  return(long(double(x) * 65535.0 / 20000000.0));
}

Thanks.

Oh, any solution where you resort to using floating point arithmetic to avoid overflow in evaluating an integer expression is IMO a bad idea.

It should be possible to reorder the integer arithmetic expression so that it avoids overflow or excessive rounding of the intermediate values. If necessary you can do a scaling divide before you do the multiplication, to avoid the multiplication overflowing. You can also use long long ints if you get desperate.

x = map(a, 5 000 000, 10 000 000, 0, 65535);

is equal to

 a -= 5000000;
x =  map(a, 0, 5 000 000, 0, 65535);

as both ranges start with 0 the mapping becomes a simple division

x = a * 65535 / 5000000;

as both 65535 and 5000000 are a multiple of 5

x = a * 13107/ 1000000;

this multiplication will overflow when a has a value of ~160.000 if a very small error is acceptable one could simplify the formula (use a spreadsheet)

x = a * 131/9994;

which overflows at values of ~ 16 000 000 which is well beyond the original range of 5M-10M

as a was a - 5 000 000 the final formula becomes

x = (a - 5000000) * 131/9994;

update: adjusted the math above (it had some bad errors)

I finally got around to needing the mapping, during testing I just used the lazy float way but I agree that the solution you've suggested is much better to limit rounding errors. Thanks.