Everyone seems to have a snazzy weekly feature these days, and we didn’t want to feel left out. Unfortunately, we could only think of a few catchy names for our series, and it’s not Friday today. Oh well. Consider it a preview publication.
Embedded systems often deal with time critical applications that require maximum performance and minimum execution time. Writing efficient code requires solid study of language and platform fundamentals, and there’s no substitute for concentrated practice. However, there are small tips and tricks to squeeze the last drop of performance out of your programs. Faster code Fridays highlights these techniques so you can implement them into your repertoire. We’ll use Arduino-compatible code for most of our examples, though these techniques will work on a number of platforms.
Alright already, we’ll all float on
Today, we’ll talk about floating-point math. Floating-point calculations involve numbers with a decimal point. Floating point numbers are often called floats, derived from the
float syntax used to assign their type. Floats are used for applications that require a great deal of precision or range, as they can represent values between 3.4028235E+38 and -3.4028235E+38. If you’re writing a program that handles analog values, such as a readout from a sensor, this precision can come in very handy.
All is not rosy in the land of decimals, however. Floats have a few drawbacks. First, all of that range and precision comes at a cost. Floats are stored as 32-bit (4-byte) variables, which is a lot of space. Many platforms also have double precision floating-point numbers, typed as
double, which take up 8-bytes of memory. It’s important to note that while the Arduino accepts
double variables, it treats them as normal floating-point numbers, so be careful if you’re trying to use the extra precision. This is a limitation of avr-gcc, and goes for 8-bit AVRs as well.
In addition, because of the way floats are stored, they aren’t exact. For example, if you assign a float variable such as
float x = 3.0, x isn’t necessarily represented as the number 3.0. It’s merely represented as close to 3.0 as the processor can achieve with the bits available. This leads to strange side effects when performing floating point math. If you were to calculate
x = 6.0/3.0, you might not get 2.0, but something very close to it. Often you must account for these small rounding errors to make sure your code works as intended.
Finally, floating point math is much, much slower than integer math on most 8-bit processors. Keeping track of all of those numbers after the decimal means that the processor can’t take the same shortcuts it can when dealing with nice whole numbers. If you store a float as
float x = 3 and then modify it somewhere, the chip will carry out calculations with all of the extra precision even if you’re not using it.
Getting grounded again
For these reasons, it’s best to avoid floating point math unless absolutely necessary. There are a few different ways to do this. The most common technique is to transform floating-point numbers to integers using a scaling factor. For example, let’s say I have a voltage readout from a sensing circuit that gives me 2 decimals of precision:
float volts = readVoltageSensor();
And I end up with values such as
volts = 3.78 or
volts = 1.76. If I know the
readVoltageSensor() function will return a precision of two decimals maximum, I can scale my voltage values by a factor of 100 and store them as integers instead:
int volts = readVoltageSensor()*100;
Now, when I perform further calculations, the Arduino will use integer math, which can be up to 40x (!) faster than it’s floating-point equivalent. Remember you have to account for the scaling factor in all operations:
// Multiply by 3 then subtract (200/100 = 2) two volts volts = (volts * 3) - 200;
If I want to display or send my result in it’s original format, I can either convert it to a local
float, or use string manipulation to insert a decimal point in the proper location:
String voltDisplay = (volts / 100); // store integer to string object voltDisplay = voltDisplay + '.' + (volts % 100); // append decimal point and hundredths Serial.println(voltDisplay);
Using Arduino String objects may not be the fastest way to do this, but it serves as a simple example. You can use integer math instead of floating-point operations in lots of different situations. The next time you’re dealing with decimals, keep an eye out for optimization opportunities, and your project will thank you for it.
Do you have an example where you were able to eliminate floats? How about suggestions for future Faster code Fridays? Let us know in the comments. And if you enjoyed this article, be sure to subscribe to our RSS feed or email newsletter.