How does "L" formatter work?

Today I learned about the "L" formatter. As in "700L".

Thing is, I was doing the following operation:

unsigned long snooze_delay;

etc

void loop() {
snooze_delay = 60 * 60 * 1000;
}

When I did that I got back a result for snooze_delay of 4294962816.

After reading the manual page on long (and bouncing around several others), I changed it to:

snooze_delay = 60 * 60 * 1000L;

And it worked. Currently running this on an Arduino Nano.

Just wanted to see if I understood it right:

  1. Whenever Arduino sees a number in code, it assumes it's an int.
  2. So in my initial expression, snooze_delay is a container that can store an unsigned long value. Which is just fine.
  3. The problem is that when I type in 60 x 60 x 1000, Arduino understands all 3 numbers as int, and allocates resources for int, and the result is classified as int automatically.
  4. The assignment occurs after the multiplication, so in the first part, Arduino does 60x60x1000, which is greater than the 32767 int limit, and the result is corrupted.
  5. The corrupted result gets assigned to my snooze_delay container, at that point any negative sign is stripped and snooze_delay ends up with an incorrect value.
  6. When I say "1000L" in the second case, Arduino allocates resources for a long value to 1000, and those resources are big enough to hold the resulting multiplication, so nothing goes wrong. Then the result, which is long, gets put into snooze_delay and that's all she wrote.

I assume that if I were using a variable in my multiplication (instead of just numbers), and that variable was of long type, I wouldn't need the "L" since already one of the values would have been assigned long resources.

That about right?

Yup. You nailed it.

1 Like

Yep pretty much. :slight_smile:

1 Like

What you write is correct. However, this is very C-style. It is not good to use so called magic numbers in the source code.

Since the Arduino IDE uses a C++ compiler, you can also use (type safe) C++ constructs:
e.g.

constexpr unsigned long SECONDS {1000}; // 1000 milliseconds
constexpr byte MINUTES {60};
constexpr byte HOURS {60};
constexpr unsigned long SNOOZE_DELAY {HOURS*MINUTES*SECONDS};

void setup() {
  Serial.begin(115200);
  unsigned long snoozeDelay = HOURS*MINUTES*SECONDS;
  Serial.print("Snooze Delay: ");  Serial.println(snoozeDelay);
}

By naming the constants, the code becomes more readable and everyone knows what the numbers mean. You don't have to guess. Since the expression snooze_delay = 60 * 60 * 1000L; contains only constants, you can also use snooze_delay as a constant wie z.B. constexpr unsigned long SNOOZE_DELAY {HOURS*MINUTES*SECONDS};

You were lucky though because 60 * 60 does fit within 2 bytes

If you had written

snooze_delay = 1000 * 60 * 60L;

You likely (can’t test right away - but I highly suspect) would still have the wrong result because 60x1000 does not fit in an int.

The best practice is to tag all the numbers to be on the safe side or understand the evaluations order rules and at least the first one in every sub expression (if you use parentheses). If you write

snooze_delay = 1000L * 60 * 60;

Then the first multiplication leads to a long and thus the second multiplication being between a long and an int is also carried out as a long

Last but not least - you should use UL (or ul) because what you really want is an unsigned long result so I would recommend

snooze_delay = 1000ul * 60ul * 60ul;

Or use typed constants. The compiler will do the math and get rid of the variables you don’t use post static maths evaluation so it won’t take more memory and is more readable

const unsigned long oneSecond_ms = 1000ul;
const unsigned long oneMinute_ms = oneSecond_ms * 60ul;
const unsigned long oneHour_ms =  oneMinute_ms * 60ul;

(You can also use constexpr instead of const to be sure of compile time evaluations)


Side note

Not completely. The number is assigned the smaller type that can represent the number so if you write

long x = 186000;

The number 186000 won’t be assumed to be an int and lead to a wrong assignment to x

So if you do

  long d1 = 3600000 * 24;
  long d2 = 60l * 60l * 1000l * 24l;
  Serial.println(d1);
  Serial.println(d2);

d1 and d2 will have the same value because the multiplication for d1 will have been done with a long since 3600000 was not fitting in an int

So... why did you "byte" for MINUTE and SECOND? Shouldn't all values associated with times be "unsigned long" ?

It's an argument I have against the "formalization" of programming.
"Writing 1000 is incorrect. What you really need is constexpr uint_least32_t milliSecondsPerSecond {1000};, because that's much more readable!" Right.

1 Like

I have chosen byte for minutes and hours, because the necessary value range is < than 255 and never negative.

Because SECONDS is defined with unsigned long or uint_least32_t, the expression SECONDS * MINUTES * HOURS will bring the correct result in every combination of the factors.

So why use eight bytes for two numbers when only two bytes are needed?

With the designation of SECONDS and milliSecondsPerSecond I agree with you completely. That is better.

That’s the theory though. Byte are promoted to int when you do maths for example (or unsigned int if the rest of the expression is unsigned) so unless you explicitly cast again your variable to byte when using it, more bytes will be really involved.

Also the optimizer will likely evaluate most of the math formula that can be evaluated at compile time so it’s possible that your constexpr will never exist really in memory.

1 Like

Hmm ok ... also once again learned something.

Thanks :slight_smile: .

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.