Five Arduino math fixes for when it is wrong

Check these when your Arduino can’t math

Arduino Math

While the Arduino library does an excellent job of hiding some of C/C++’s warts, at the end of the day, it is still just C/C++. This fact causes a few non-intuitive issues for inexperienced programmers. When it looks like Arduino math is wrong, it is probably one of these reasons.

When people ask me for help with their programming, I check each of these Arduino math mistakes. If your code seems to be hitting a bug, check to make sure it is not how the compiler handles math.

Before I start, I want to point out that these issues are not “Arduino” issues. These are related to how C compilers handle variables and math. With that said, I am no compiler expert so these are all learned from experience.

Variable types

Before getting into why Arduino math does not work, I need to cover a brief explanation on variable types. If you know the difference between integer and float, you can skip down to the next section.

Computers have a difficult time with decimal numbers. Binary representations work great for integers like 1,2,3,4,5, etc. Decimal points, however, add complexity to the situation. For that reason, numeric variable types are known as “integer” or “floating point.” Integer types can only handle, well, integers, while floating point can hold decimal numbers.

All of the examples here are using an Arduino Uno. That board is based on the Microchip ATmega328p.

#1 Long Arduino delay()s don’t work

Skipping right passed delay() vs. millis(), a common action is doing a delay that lasts hours or days. This problem seems to come up with projects like sprinklers or aquariums. Instead of doing the math ahead of time, you might try letting the computer do the work for you.

// 1000 ms, 60 sec, 60 min, 24, hours, 14 days
unsigned long wait = 1000 * 60 * 60 * 24 * 14;
Serial.print("wait = ");
Serial.println(wait);

// Prints wait = 2048

The code seems logical and straightforward. It should calculate the number of milliseconds in fourteen days, which is: 1,209,600,000 milliseconds. An unsigned long can hold a value up to 4,294,967,295. So there should be no problem there.

If an unsigned long can hold the value, why did Arduino math calculate 2048? Well, even though you plan to store the result of the math operation in an unsigned long variable type, the compiler does not care. It performs the operation on the right side of the equal sign before storing the value into the desired variable.

The way that C/C++ handles this situation is that the constants, “1000”, “60”, “24”, and “14” will all fit into an integer data type. So it performs the multiplication as “integer math.” Which means storing each intermediate math operation in an integer. Eventually, those results overflow variable as small as an integer.

If we tell the compiler to treat the constants as an unsigned long, instead of a teeny integer, then the intermediate results are stored in a larger variable type. An easy way to promote the operation is to use the modifier “UL” like below.

// 1000 ms, 60 sec, 60 min, 24, hours, 14 days
unsigned long wait = 1000UL * 60 * 60 * 24 * 14;
Serial.print("wait = ");
Serial.println(wait);
// Prints: wait = 1209600000

You need to be careful where you place the “UL.” If you place it at the end, the result is still an overflow because of the order in which the math occurs. You could also add “UL” to all the constants. More on that in a bit.

unsigned long wait = 1000 * 60 * 60 * 24 * 14UL;
// Prints: wait = 329728

By the way, pay attention to compiler warnings. It may try to tell you about this issue:

 warning: integer overflow in expression [-Woverflow]
   unsigned long wait = 1000 * 60 * 60 * 24 * 14;

There are other methods to force wider data types to be used. This method is my favorite. Next, we look at a related issue that occurs when mixing integers and floats.

#2 Why does Arduino division result in a zero?

Look at this code example. What do you expect the serial monitor to print?

float example = 3 / 5 * 2;
Serial.print("example = ");
Serial.println(example);
// Prints: example = 0.00

You might assume that the serial monitor prints “1.20”. Afterall, that is the answer you get when you plug it into your calculator. However, the Arduino math comes back with “0.00”. Why does that happen?

Similar to the example above, all of the constants on the right of the equal sign are integers. So each intermediate math operations get stored an integer data type.

A straightforward way to fix this problem is to tell the compiler that one of the constants is a floating point variable. Just adding “.0” promotes the operation to floating point math. Later I’ll talk about preference order which makes a difference on where you add the .0. If you want to be safe, add it to all integer constants.

float example = 3.0 / 5.0 * 2.0;
Serial.print("example = ");
Serial.println(example);
// Prints: example = 1.20

What happens if the variable “example” changes into an integer? The decimal point does not get stored. Let’s look at two examples.

#3 Arduino math does not Round

Starting off, here is the same math operation from above but with “example” declared as an integer instead of a float.

int example = 3.0 / 5 * 2;
Serial.print("example = ");
Serial.println(example);
// Prints: example = 1

An important thing to realize is that when doing decimal math that ends up in an integer, the decimal is truncated. The decimal is not rounded. For the second example I changed the math a little bit.

int example = 5.0 / 3;
Serial.print("example = ");
Serial.println(example);
// Prints: example = 1

Even though the result is actually “1.666…”, Arduino math returns “1” not “2.” The integer does not get rounded up (or down) based on the decimal. The code truncates or drops, the decimal entirely. (Note that there are rounding functions are available in Math.h.)

#4 Arduino’s float Precision

Floating point variables mean that the decimal point can float around. The precision of the number is limited to a certain number of digits. That is not the same as saying a certain number of decimals. In the case of 8-bit AVR Arduino boards, the float type is 32-bits which makes the limit 6 or 7 digits.

float example1 = 1.23456789;
float example2 = 123456.789;
Serial.print("example1 = ");
Serial.println(example1,9);
Serial.print("example2 = ");
Serial.println(example2,4);

// Prints:
// example1 = 1.234567880
// example2 = 123456.7890

Here is the result from my serial monitor.

Arduino float losing precision

Arduino float losing precision

Even though “example1” and “example2” have the same number of digits, the decimal point is in a different place. Notice that example1 lost a tiny bit of precision at the end of the digit string. The last valid digit is an “8” and not a “9”.

Meanwhile, “example2” was okay.

You might be thinking that a double is better than a float since the variable type is twice as big. On the 32-bit based Arduino platforms, this point might be the case. However, on the 8-bit AVR based boards, the avr-gcc compiler does not support the double type. So a float and a double work the same.

#5 Order of operations

I do not want to turn this into a comment-bait Facebook post. C does have a defined operator preference. Basic operators occur in the order of Multiplication and division, then addition and subtraction. Check this reference for a complete list of operator precedence. Parenthesis, of course, is calculated first.

Based on what you learned in this post and knowing there is a specific order of operations, what value does this code generate? The answer may shock you!

int example = 3 * ((5 + 2) / 4) - 1;
Serial.print("example = ");
Serial.println(example);

Keep parenthesis and the order of operations in mind when you try to promote constants to larger variable types. You need to add “UL” or “.0” to the first operation to make sure the entire operation gets promoted.

These are the most common issues I have run into with Arduino math. In most cases the math is working fine, it is the compiler doing something I did not expect. Next time your math seems to be off, double-check these points. Or float-check them!

Question: What Arduino or C-related math ‘issues’ have you run into wiht your code? You can leave a comment by clicking here.

Related

You might also want to check out my related post, Arduino Myths everyone believes but are not true.. It explains why I call it the “Arduino Library” and not its own language.

Long comments, URLs, and code tend to get flagged for spam moderation. No need to resubmit.

Leave a comment

9 thoughts on “Five Arduino math fixes for when it is wrong

  1. Hello James,

    I’ve got a dum problem with simple addition.

    Using float variables my math operation is

    Dis = Dis + D;

    It works fine until the results gets higher than 100.00. After that, only results bigger than 1.0 gets added.

    Tha’s one thing. The main issue is when the result gets higher than 1000.00. It simply returns to 500.00

    I don’t know if the size of the results is limited to this 5 digits. I tried long, short, byte, but that doesn’t seem to solve it.

    I did an upgrade at my upcoming math, adding the .0 to all my constants, but no progress.

    I’m using an arduino UNO, and my program is a GPS Tracker, that calculate the live distance ante avarage speed.

    Thank you.

  2. Loved this! It is always good to know what you are doing with different data types, you are giving very good hints! Just a small criticism: in your example with floating points, you are doing the operation using double precision — for instance 3.0 / 5.0 * 2.0 is different from 3.0f / 5.0f * 2.0f. Nevertheless, these operation will be almost surely evaluated at compile time, so it is generally difficult to get some observable differences between the two ways of writing the code.
    Oh, just for you curiosity (maybe you are already aware of that): give a look to the Rust programming language, it is becoming bigger every day in the embedded world. The big downside for what you generally do is that the AVR support is actually… meh.

    • you are doing the operation using double precision — for instance 3.0 / 5.0 * 2.0 is different from 3.0f / 5.0f * 2.0f.

      I forgot that .0 was actually a double. Of course in the realm of avr-gcc that does not matter, but a good point to know. Thank you for clarifying.

      give a look to the Rust programming language

      Thank you for the heads-up. I am working on some stuff with a tentative title of “beyond Arduino.” Currently I am learning the MSP430, next on my list is the STM32, and eventually ARM. From the quick look I did, I might include a look at Rust in that series.

  3. Great article! Regarding #5, multiplication & division have the *same* precedence, in order from left to right, just as addition and subtraction have the same precedence in order from left to right. An example, (4 / 2 * 2) would equal 1 if multiplication had a higher precedence, but is actually 4 as precedence is the same and is read left to right.

    Keep up the awesome blog!

  4. About your last example. I thought it should print “example = 2”. Then I looked again and realised it will not compile, because “Int” is not a defined type. When I fixed the compile problem, it did as I expected.