22
\$\begingroup\$

As a rule of thumb, I try to avoid using floating-point in my embedded system codebase.

Floating-point variables are:

  • Computation-intensive
  • Not atomic (can cause problems in an RTOS application or with interrupts)
  • Their precision can cause non-obvious behaviour (float comparison problem).

But what about a microcontroller with a floating point unit (like the STM32F4)?

Do those concerns still apply? Would you still advise against using floating-point?

\$\endgroup\$
16
  • 7
    \$\begingroup\$ Points (2) and (3) still apply. So, not so much "avoid altogether" but "use with your eyes open" to avoid problems with atomicity or unreliable operations. (And never use floats as loop variables!) \$\endgroup\$
    – user16324
    Commented Apr 16, 2020 at 15:24
  • 22
    \$\begingroup\$ You should choose your MCU to match your application rather than design your application to match your MCU. So if you can avoid floating point operations then you can choose an MCU without an FPU, and probably reduce the cost of your system. \$\endgroup\$
    – The Photon
    Commented Apr 16, 2020 at 15:55
  • 3
    \$\begingroup\$ _Atomic float works similarly to _Atomic int32_t as far as atomicity and ordering, and is lock-free on normal ARM CPUs. If you think plain int is safe to use in C in general, think again. e.g. MCU programming - C++ O2 optimization breaks while loop. Re: atomic floating point - compiler/language support is basically the same as in C++: Atomic double floating point or SSE/AVX vector load/store on x86_64 / C++20 std::atomic- std::atomic.specializations \$\endgroup\$ Commented Apr 17, 2020 at 4:50
  • 1
    \$\begingroup\$ @CodyGray: I was thinking about this "non atomic" claim some more. I wonder if people mean that some soft-float libraries are not re-entrant, and thus can break if an interrupt happens mid calculation even without accessing shared memory (if interrupt handlers also use FP, or you context switch)? That would make some sense (for cases / ISAs where you can't just use stack space for temporaries, either too big or no convenient stack-relative addressing in old 8-bit micros). If so, it's not a clear way to describe it, especially in C terms. \$\endgroup\$ Commented Apr 18, 2020 at 2:15
  • 8
    \$\begingroup\$ “can cause non-obvious behaviour” – that's not a reason not to use floats, but to properly learn about their behaviour. In lots of applications, floats are the best fit if you can afford them. Fixed-precision just gives you another set of non-obvious behaviours which are in practical applications often much worse. \$\endgroup\$ Commented Apr 18, 2020 at 9:44

9 Answers 9

16
\$\begingroup\$

If you buy a processor with a hardware FPU, you don't have the same concerns about precision*, reentrant behaviour etc. Go ahead and use them!

Couple of thoughts though:

  • You might consider that the processor can power down the (large) FPU when it's not used, so check that running your FP routines saves you power (if you care about that) over doing it in software.

  • Depending on the implementation, the FPU might also have different registers to the core - sometimes compilers can make clever use of these.

  • Don't use the FPU as a crutch for bad firmware design. For example, could you do the same thing with fixed point, and use the normal core instead?

(* The FPU should conform to a given standard implementation, so be aware of any limitations arising from that.)

\$\endgroup\$
3
  • 4
    \$\begingroup\$ +1 For the power consumption. I didn't though about this tradeoff. Thanks. \$\endgroup\$
    – gbt
    Commented Apr 16, 2020 at 18:27
  • 5
    \$\begingroup\$ Note that blind assumptions like "the FPU uses more power" can often get you into trouble. Emulating FPU operations with fixed-point routines may end up using more cycles, and therefore more power, than if you'd simply used the built-in efficient hardware FPU. And while I'm sure there are things out there I'm not aware of, all the MCUs I've seen with hardware FPUs have dedicated floating-point registers (i.e., they don't overlap with the "core" integer registers). I absolutely agree, though, that floating point should not be used as a crutch for bad design. Use the right tool for the job. \$\endgroup\$
    – Cody Gray
    Commented Apr 18, 2020 at 9:57
  • 1
    \$\begingroup\$ @CodyGray - absolutely, that's why I said "check" :) Chances are, if you're using FP, then the FPU will be more power efficient, but it also might not be, say if you're just doing a single operation. Micros are so advanced now, there are lots of places assumptions will lead you astray - all good fun though! \$\endgroup\$
    – awjlogan
    Commented Apr 18, 2020 at 10:04
26
\$\begingroup\$

You should remember that the FPUs on these microcontrollers are often just single-precision FPUs. Single precision floating-point has only a 24 bit mantissa (with the hidden MSB) so you may get better precision from 32 bit integers in some cases.

I have done work with using fixed-point arithmetic, and for situations where the data has a limited dynamic range you can achieve the same precision as single-precision floating point using 32-bit fixed point with about an order of magnitude improvement in execution time. I have also seen that the compiler drags in a fair amount of library overhead for the FPU.

\$\endgroup\$
3
  • 6
    \$\begingroup\$ +1 Could be useful to note that the Cortex M4 support packed data types and DSP instructions (e.g. multiply accumulate) that can give you massive improvements if you can work in fixed point, even if an FPU is present. Also note STM32F7 has double precision FPU, but you run into memory bottlenecks quicker as it is still a 32-bit system. \$\endgroup\$
    – Jon
    Commented Apr 16, 2020 at 16:01
  • \$\begingroup\$ +1 Interesting point. I didn't consider the fact that FPU would maybe only support single-precision floating-point and not double. \$\endgroup\$
    – gbt
    Commented Apr 16, 2020 at 18:25
  • 4
    \$\begingroup\$ @gberth This is common on many micros. FWIW you also need to have the same concern about integers. 16-bit and 32-bit micros do not support 64-bit integers, and some do not have support for integers smaller than the word length. All these operations have to be handled by the compiler instead of as atomic instructions. \$\endgroup\$
    – Graham
    Commented Apr 17, 2020 at 7:06
13
\$\begingroup\$

Some of the concerns still apply.

  • Floating-point arithmetic is inherently more computation-intensive than integer. But with a floting-point unit, you probably won't notice that any more, maybe a few additional cpu cycles or a bit more power consumption.
  • Operations are atomic, so that concern is gone.
  • the precision / rounding / comparison problem is still there, to exactly the same amount as in software computation.

Especially the latter one can cause very nasty problems, and force you to write non-intuitive code, e.g. always comparing against a range, never testing equality against a fixed value.

And remember that a single-precision float only has 23 bits resolution, so you might need to replace a 32-bit integer with a double-precision float.

\$\endgroup\$
6
  • \$\begingroup\$ +1 For the mention of "non-intuitive code". Even if my question mainly focused on performance and robustness, I think its important to also think about code clarity. \$\endgroup\$
    – gbt
    Commented Apr 16, 2020 at 18:29
  • 4
    \$\begingroup\$ Re "non-intuitive code", that just means choosing the appropriate type for what you're doing. If your values are not exact (e.g. measuring a voltage) then exact comparison is inherently wrong. Using tolerances doesn't make the code non-intuitive, it makes it correct. \$\endgroup\$
    – Graham
    Commented Apr 17, 2020 at 7:15
  • 1
    \$\begingroup\$ Agree with Graham. Comparing a voltage in integer millivolts is likely wrong for exactly the same reason. That said, your ADC is likely producing integer measurements. \$\endgroup\$
    – MSalters
    Commented Apr 17, 2020 at 10:20
  • 1
    \$\begingroup\$ Floating-point arithmetic is inherently more computation-intensive than integer, if you're calculating with integers. If your problem is with floating point numbers, then the highly-tuned FPU is going to be much faster doing all the scaling in software; that's why FPUs exist. \$\endgroup\$
    – prosfilaes
    Commented Apr 17, 2020 at 23:57
  • 1
    \$\begingroup\$ Can you give an example of an architecture where floating-point operations are atomic? There are no such guarantees on either x86 or ARM. I'm less familiar with MIPS and the other more esoteric architectures out there. But in general, you cannot make the assumption that any operations are going to be atomic, including integer operations. Even something as simple as a load or a store is often broken up into multiple operations due to memory bus limitations (on a 32-bit MCU, you cannot load a 64-bit double-precision floating-point value as an atomic operation). \$\endgroup\$
    – Cody Gray
    Commented Apr 18, 2020 at 10:00
11
\$\begingroup\$

Calculations are often fine if you have the FPU, and the trade-offs are easy to understand.

But watch for the output. If you've got anything like the C library, you'd be amazed at the complexity inherent in printf("%0.6g", x); I've seen libraries which used malloc() inside printf(), and that's not the kind of thing you'd like in a microcontroller.

\$\endgroup\$
3
  • \$\begingroup\$ Unfortunately, the Standard requires that floating-point arguments to printf-family functions be output using a double type that must be at least 48 bits wide even on platforms which just have a 32-bit floating-point hardware. \$\endgroup\$
    – supercat
    Commented Apr 17, 2020 at 21:48
  • \$\begingroup\$ And it uses global variables (shared state)? \$\endgroup\$ Commented Apr 17, 2020 at 23:08
  • 1
    \$\begingroup\$ This is a very good point about the need to watch out for bloated C std lib implementations when targeting embedded MCUs. Unfortunately, it's not limited to floating-point operations. Something like printf is likely to be an absolute disaster in a severely constrained environment. Fortunately, you aren't going to be using that in production. If you use it at all, it'll be for debugging, where performance isn't critical. When you care about performance, you'll need to be very aware of what your C compiler is generating or write the asm yourself. Usually you care less than you think, though. \$\endgroup\$
    – Cody Gray
    Commented Apr 18, 2020 at 10:03
7
\$\begingroup\$

Honestly this is a type of micro-optimization you should be making only after you have a fully working codebase. Some MCU's have issues with division as well, even with integers. So doing something like "multiply fp by 100, do some manipulation, divide by 100" may take much more time than just manipulating the float.

This is where profiling comes in, you need to pick your battles, there is no one answer. After you have a working code-base, you can identify bottlenecks and optimize selectively. Avoiding something as a blanket statement leads to micro optimizations that take more time to code than they actually save. Optimizing out floats from a low-priority routine that runs once an hour is useless, whereas optimizing out floats for a heavy task is useful.

\$\endgroup\$
4
  • 6
    \$\begingroup\$ Hopefully if you're doing fixed-point, you'd use powers of 2 rather than powers of 10. Instead of "multiply by 100, operate, divide by 100", you should multiply by 128, operate, and divide by 128. I don't know of any MCU that can't divide by 128 efficiently. \$\endgroup\$
    – The Photon
    Commented Apr 16, 2020 at 15:57
  • 1
    \$\begingroup\$ @ThePhoton Yes, it was just a quick example, and I would think that a good compiler can optimize a /128 to >> 8 before it even gets to the processor, I just picked an arbitrary value. \$\endgroup\$
    – Ron Beyer
    Commented Apr 16, 2020 at 16:08
  • 1
    \$\begingroup\$ Division by a constant is not much harder than multiplication. It can be done in terms of a multiplication and shifts, compare e.g. the ARM code for these two functions. Well, at least that's true when you have an instruction to do multiply(32bit,32bit)→64bit. \$\endgroup\$
    – Ruslan
    Commented Apr 17, 2020 at 10:13
  • \$\begingroup\$ @Ruslan: Unfortunately, many Cortex-M0 cores include a hardware for a one-cycle 32x32->32 multiply, but no efficient means of computing the upper word of a 32x32 product. I would think hardware that could perform a four-cycle multiply yielding either the upper or lower half of the result would have been cheaper and more useful, but perhaps not as appealing to the marketing department. I also find it somewhat curious that chip vendors can request either a one-cycle or 32-cycle multiply, but there's no option for a ~18-cycle multiply, which would be almost as cheap as 32-cycle one. \$\endgroup\$
    – supercat
    Commented Apr 22, 2020 at 16:15
3
\$\begingroup\$

Basically no need to avoid if you have a FPU, and your RTOS supports context switching of the FPU too. The precision issue still exists if you have a FPU or not. You can freely use floating point without FPU too if you have the performance to do it - occasional debug write of float variable is just fine on a FPU-less Cortex-M3. But obviously on a limited 8-bit MCU with small memory, the overhead of using even a single float operation can bring many hundred bytes of soft float library code in, so sometimes using floats makes no sense.

\$\endgroup\$
3
  • \$\begingroup\$ Assuming the FPU operations are atomic, what additional work the RTOS need to do to support floats? \$\endgroup\$
    – gbt
    Commented Apr 16, 2020 at 18:31
  • 4
    \$\begingroup\$ Depends on the FPU programming model of course - but when a RTOS switches from one task to another, it must save all CPU registers somewhere (task structure), and restore all CPU registers of the next task. With a FPU, the task switch must store and load the FPU registers too, so the task switching process must store and restore more registers. \$\endgroup\$
    – Justme
    Commented Apr 16, 2020 at 19:31
  • 2
    \$\begingroup\$ If a CPU supports lazy context switches like Cortex-M4, the RTOS doesn't necessarily have to do FPU stores and loads on context switch. \$\endgroup\$
    – Ruslan
    Commented Apr 17, 2020 at 10:23
2
\$\begingroup\$

When to not use floating point

The first thing one needs to realize is that floating point does not mean "I need decimals". This is where some 95% of all would-be embedded programmers misusing floating point fail.

The cure for that disbelief is to realize that internally, the program should use a unit that makes sense for the MCU to use, not one that makes sense to humans.

For example if you measure current in mA with an on-chip 10 bit ADC, the convenient unit to use in software is "fixed point raw ADC values from 0 to 1024". In C programming that means a uint16_t or optionally a uint_fast16_t. Not an int and certainly not a float.

Using the unit mA inside firmware calculations is only convenient for the human programmer's brain, in case it can't handle abstract units. But it is inconvenient for the program, because it means you need to re-scale all readings to and potentially add rounding inaccuracy while doing so. Plus the scaling code is just overhead bloat. And it will likely include division, which can be painful for many MCUs.

Yeah you are reading the current in mA. But unless you actually need to print that current on a display or something to a human user, that unit is actually not helpful. Do the mA re-scaling on pen and paper while designing the algorithm, instead of dragging it into your firmware.


When to use floating point

  • If your MCU has a FPU and you actually need to do advanced math, then you should use floating point. Otherwise you should not.

"Advanced math" doesn't necessarily mean advanced from the programmer's perspective, but from the software's perspective. "Advanced" includes things like square roots, geometry or trigonometry, the use of math.h in general, complex numbers, AI math etc. Things that would be painful to implement in fixed point.

And if you don't have an FPU but need floating point, you picked the wrong MCU for your project.

\$\endgroup\$
0
\$\begingroup\$
  • I would exclude the non-atomic operations from the list of reasons since your ways of handling other complex structures also can be applied to float operations.

  • I would also exclude the computational power needed from the list of reasons since that is highly application and processor specific.

Lets focus on the "non-obvious" problems caused by the varying of the float precision

  • The most fundamental one is that FP arithmetic is non-associative, (a+b)+c is not equal to a+(b+c). Imagine a=1,b=-1,c=1e-20. Sounds harmless, but imagine you application is using a finite impulse filter and you run a test case which is suppose to be exactly zero

  • for me the second reason is the fact that integer and fixed comma operations have an overflow at the range which I expect (ok, not enabled by default in C). e.g. In floating point an integrator can easily run away to very large values without anybody noticing...

\$\endgroup\$
0
\$\begingroup\$

Like many things, it depends on your use case.

A bespoke integer algorithm can be more efficient (e.g. for a multiply) if you know the allowed range of input values. As those values widen, whatever you've coded is going to degenerate into a floating point multiply without benefit of hardware.

It's possible to deal with slow calculations having hard to determine timings on an embedded system by running them in a background loop, or even having different processors for e.g. control and calculation.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.