Map() giving wrong answers

I was thinking how fun it would be to save hour min sec in a two byte integer which normally is impossible because there are 86400 in a day and unsigned integer has only 65536 possible values. but what if I simply map the range, to the possible values, some degree of precision will be lost but that is not important. Well here's what I got using this code.

long  UTCHour   = 18;
long  UTCMinute = 59;
long  UTCSecond = 35;
long  in;
long  out;
void setup() {
  Serial.begin(115200);
  delay(8000);  //allow serial to start lest some stuff become missing.
}
void loop() {
  Serial.println("Start  ");
  Serial.print(" in UTCHour ");      Serial.println(UTCHour);
  Serial.print(" in UTCMinute ");    Serial.println(UTCMinute);
  Serial.print(" in UTCSecond ");    Serial.println(UTCSecond);
  in        = (UTCHour * 60 * 60) + (UTCMinute * 60) + UTCSecond;//total seconds
  Serial.print("total seconds in "); Serial.println(in);
  out       = map( in, 0, 86400, 0, 65535);                      //fit to a 2 byte integer
  Serial.print("after map 1 ");      Serial.println(out);

  out       = map(out, 0, 65535, 0, 86400);                     //expand back to original
  Serial.print("after map 2 ");      Serial.println(out);
  UTCHour   = out / 3600;
  Serial.print("out UTCHour ");      Serial.println(UTCHour);
  out       = out - (UTCHour   * 3600);
  UTCMinute = out / 60;
  Serial.print("out UTCMinute ");    Serial.println(UTCMinute);
  UTCSecond = out - (UTCMinute * 60);
  Serial.print("out UTCSecond ");    Serial.println(UTCSecond);
  Serial.println();

  UTCHour = random(24); UTCMinute = random(60); UTCSecond = random(60);
  delay(8000);
}

but here's the output, the first is perfect I get the same thing I put in, but not in the second one. What am I doing wrong. I think it must have to do with the signed unsigned thing but with long there are enough positive numbers to handle this I would think.

Start  
 in UTCHour 5
 in UTCMinute 44
 in UTCSecond 48
total seconds in 20688
after map 1 15692
after map 2 20688
out UTCHour 5
out UTCMinute 44
out UTCSecond 48

Start  
 in UTCHour 10
 in UTCMinute 34
 in UTCSecond 32
total seconds in 38072
after map 1 -20831
after map 2 -27462
out UTCHour -7
out UTCMinute -37
out UTCSecond -42

Any ideas?

Hi,
Map function is long from what I believe.

For the mathematically inclined, here’s the whole function

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

Tom.. :smiley: :+1: :coffee: :australia:

but why does it work for some values and not others. and why the negative numbers?

Should I try making my own function with unsigned numbers?

Thanks. I put that code in my own function, and placed the OP's test in the wokwi for convenince.

If map() is your problem, which it may well be, lose all the clutter and boil it down for experimentation.

void loop() {

  in =  20000;

  Serial.print("total seconds in ");
  Serial.println(in);

  out = map( in, 0, 86400, 0, 65535);

  Serial.print("after map 1 ");
  Serial.println(out);

  out  = map(out, 0, 65535, 0, 86400);

  Serial.print("after map 2 ");
  Serial.println(out);

  for (;;);  // or just put the code in setup();
}

In the wokwi simulator

map function test in the sim

you can just edit and restart the code, makes it painless and perhaps obvious.

On the other hand, why not just divide the number of seconds by two, you'll get a loss of precision, natch, but a consistent and maybe acceptable result. Give anyone who notices the seconds are always even for a second a cookie.

a7

i get a negative value if the result from map() is captured in a long, but not if it is unsigned

20688 15691
38072 44704

20688 15691
38072 -20832
void
func (
    long  hr,
    long  min,
    long  sec )
{
    sec += (60 * min) + (60 * 60 * hr);

    long mapSec = map (sec, 0, 86400, 0, 65535);

    Serial.print (sec);
    Serial.print (" ");
    Serial.println (mapSec);
}

void
setup (void)
{
    Serial.begin (9600);

    func ( 5, 44, 48);
    func (10, 34, 32);
}

void
loop (void)
{
}

void
func (
    long  hr,
    long  min,
    long  sec )
{
    sec += (60 * min) + (60 * 60 * hr);

    long mapSec = map (sec, 0, 86400, 0, 65535);

    Serial.print (sec);
    Serial.print (" ");
    Serial.println (mapSec);
}

void
setup (void)
{
    Serial.begin (9600);

    func ( 5, 44, 48);
    func (10, 34, 32);
}

void
loop (void)
{
}

Hi,
Don't forget;
map() has no constraints, it will calculate passed the max and mins if the input variables do so.

Tom.. :smiley: :+1: :coffee: :australia:

Certainly a thought, funny I was using wokwi too on this.

Let me try that for fun. go uint32_t for the calculations then move it back to uint16_t for storage.

Odd. I did not confirm this, so obviously there is something I am doing wrong. Or differetn.

Yes, a negative result. If you receive it as unsigned long, you get a huge number obvsly, then all bets are off.

At some point 40000 * 86400 is caculated, that's a 32 bit number, so the sign bit gets dragged in.

My success was to change all long variables to unsigned long, in the code of map() and elsewhere.

constraint() is important when mapping, thanks @TomGeorge for the reminder, but does not enter into this.

TBH I'd just divide seconds by two. I have never liked the odd behaviours of the map() function.

a7

I think I figured it out, after about a day lol, but you can run this you and see that in step3 it blows eventually.

uint32_t  UTCHour   = 0;
uint32_t  UTCMinute = 59;
uint32_t  UTCSecond = 35;
uint32_t  in;
uint32_t  out;
uint32_t umap(uint32_t x, uint32_t in_min, uint32_t in_max, uint32_t out_min, uint32_t out_max) {

  uint32_t step1 = x       - in_min;
  uint32_t step2 = out_max - out_min;
  uint32_t step3 = step1   * step2;
  uint32_t step4 = in_max  - in_min;
  uint32_t step5 = step4   + out_min;
  uint32_t step6 = step3   / step5;
  Serial.print("step1 ");      Serial.println(step1);
  Serial.print("step2 ");      Serial.println(step2);
  Serial.print("step3 ");      Serial.println(step3);
  Serial.print("step4 ");      Serial.println(step4);
  Serial.print("step5 ");      Serial.println(step5);
  Serial.print("step6 ");      Serial.println(step6);

  return step6;
  //return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

void setup() {
  Serial.begin(115200);
  delay(8000);  //allow serial to start lest some stuff become missing.
}
void loop() {
  Serial.println("Start  ");
  Serial.print(" in UTCHour ");      Serial.println(UTCHour);
  Serial.print(" in UTCMinute ");    Serial.println(UTCMinute);
  Serial.print(" in UTCSecond ");    Serial.println(UTCSecond);
  in        = (UTCHour * 60 * 60) + (UTCMinute * 60) + UTCSecond;//total seconds
  Serial.print("total seconds in "); Serial.println(in);
  out       = umap( in, 0, 86400, 0, 65535);                      //fit to a 2 byte integer
  Serial.print("after map 1 ");      Serial.println(out);

  out       = umap(out, 0, 65535, 0, 86400);                     //expand back to original
  Serial.print("after map 2 ");      Serial.println(out);
  UTCHour   = out / 3600;
  Serial.print("out UTCHour ");      Serial.println(UTCHour);
  out       = out - (UTCHour   * 3600);
  UTCMinute = out / 60;
  Serial.print("out UTCMinute ");    Serial.println(UTCMinute);
  UTCSecond = out - (UTCMinute * 60);
  Serial.print("out UTCSecond ");    Serial.println(UTCSecond);
  Serial.println();
  static uint8_t h = 17;
  UTCHour = h; UTCMinute = random(60); UTCSecond = random(60);
  h++;
  delay(8000);
}

yeah, it's funny we, well at least I, assume these published functions will work properly under any conditions but it isn't necessarily true is it!

 x  32767, mapInt  24853, mapLong 24853, mapUns  24853
 x  32768, mapInt  24854, mapLong 24854, mapUns  24854
 x  32769, mapInt -24854, mapLong -24854, mapUns  40682
char s [80];
void
func (unsigned x)
{
    int      mapInt  = map (x, 0, 86400, 0, 65535);
    long     mapLong = map (x, 0, 86400, 0, 65535);
    unsigned mapUns  = map (x, 0, 86400, 0, 65535);

    sprintf (s, " x %6u, mapInt %6d, mapLong %ld, mapUns %6u",
        x, mapInt, mapLong, mapUns);
    Serial.println (s);
}

void
setup (void)
{
    Serial.begin (9600);

    func (32767);
    func (32768);
    func (32769);
}

void
loop (void)
{
}

isn't that bizarre.

Well I got it to work, change everything in the function to long long.

uint32_t  UTCHour   = 0;
uint32_t  UTCMinute = 59;
uint32_t  UTCSecond = 35;
uint32_t  in;
uint32_t  out;
uint32_t umap(uint32_t x, uint32_t in_min, uint32_t in_max, uint32_t out_min, uint32_t out_max) {

  long long step1 = x       - in_min;
  Serial.print("step1 ");      Serial.println(step1);
  long long step2 = out_max - out_min;
  Serial.print("step2 ");      Serial.println(step2);
  long long  step3 = step1 * step2;
  Serial.print("step3 ");      Serial.println(step3);
  long long step4 = in_max  - in_min;
  Serial.print("step4 ");      Serial.println(step4);
  long long step5 = step4   + out_min;
  Serial.print("step5 ");      Serial.println(step5);
  long long step6 = step3   / step5;
  Serial.print("step6 ");      Serial.println(step6);
  return step6;
  //return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

void setup() {
  Serial.begin(115200);
  delay(8000);  //allow serial to start lest some stuff become missing.
}
void loop() {
  Serial.println("Start  ");
  Serial.print(" in UTCHour   ");    Serial.println(UTCHour);
  Serial.print(" in UTCMinute ");    Serial.println(UTCMinute);
  Serial.print(" in UTCSecond ");    Serial.println(UTCSecond);
  in        = (UTCHour * 60 * 60) + (UTCMinute * 60) + UTCSecond;//total seconds
  Serial.print("total seconds in "); Serial.println(in);
  out       = umap( in, 0, 86400, 0, 65535);                      //fit to a 2 byte integer
  Serial.print("after map 1 ");      Serial.println(out);

  out       = umap(out, 0, 65535, 0, 86400);                     //expand back to original
  Serial.print("after map 2 ");      Serial.println(out);
  UTCHour   = out / 3600;
  Serial.print("out UTCHour   ");    Serial.println(UTCHour);
  out       = out - (UTCHour   * 3600);
  UTCMinute = out / 60;
  Serial.print("out UTCMinute ");    Serial.println(UTCMinute);
  UTCSecond = out - (UTCMinute * 60);
  Serial.print("out UTCSecond ");    Serial.println(UTCSecond);
  Serial.println();
  static uint8_t h = 17;
  UTCHour = h; UTCMinute = random(60); UTCSecond = random(60);
  h++;
  delay(8000);
}

have you tried "unsigned"?

Yes. Just unsigned long has ankther problem after 49710 seconds last midnight… about 1:50 PM.

That's when the 32 bit long is overblown:

ln(86400*49710)/ln(2)

is 31.9…

a7

try the one I just posted, I seem to be getting good numbers now.

Too much clutter, not going through that again, sry.

Did you try a time after 1:50? I thought you were using long long variables…

OIC. Your code cut and pasted in to a blank sketch does not compile.

a7

yes, with long long anything seems to work to wit:

Start  
 in UTCHour   160
 in UTCMinute 35
 in UTCSecond 45
total seconds in 578145
step1 578145
step2 65535
step3 37888732575
step4 86400
step5 86400
step6 438526
after map 1 438526
step1 438526
step2 86400
step3 37888646400
step4 65535
step5 65535
step6 578143
after map 2 578143
out UTCHour   160
out UTCMinute 35
out UTCSecond 43


obviously eventually it will break, but this is good enough for now. Methinks the documentation should be very explicit in pointing out the pitfalls of the published function because while nothing I do is life or death, this could wind up in an airplane autopilot, or power plant, or manufacturing process and survive testing long enough to go into production and oops, somebody died.

I agree. Although I certainly hope no Arduinos are any part of, say, the Boeing 737 Max's Maneuvering Characteristics Augmentation System... and that testing would at least find a problem like yours where it clearly doesn't work after a certain TOD.

But I did see

Longtime Boeing engineers say the effort was complicated by a push to outsource work to lower-paid contractors.

$9 an hour. Cough!

After some other kinds of "excitement" with the map() function I don't use it.

You can see from the code that it is a simple enough function; there are ppl who could just look at it and predict where (and how) it will fail. I suppose the documentation don't go into that because, well, it is inexcusable, truly, life threats in the picture or not. Look at how much time you and we wasted spent on it!

One thing that surprised me was the point @TomGeorge raised - the equations that inform the result will cheerfully extrapolate and return values no matter where the input is with respect to the input range. Hence his advice to keep the constrain() function in mind.

Another place it does not satisfy is mapping from or to a very small set of values. Not all numbers get equal treatment - the high return value might obtain in only one case among many, or something like that: I've forgotten more than I ever knew about it and have not (until this day!) looked back.

a7