2
\$\begingroup\$

I'm using STM32F103C8T6 and my compiler is KEIL 5. I have written a simple program to check something but I found out an interesting point. When I download the program to the MCU, aaa and bbb have to be 12.56 and 62.8, but when I debug the program on the MCU, the initial values of these variables becomes these: aaa=12.5600004 and bbb=62.8000031. the program:

#include "stm32f10x.h"
float aaa=12.56,bbb;

int main(void)
{
    bbb=aaa*5;
    while(1){}
}

Also keep in mind if I change float to double, it works without problem.

\$\endgroup\$
  • 9
    \$\begingroup\$ Correct. \$\endgroup\$ – Ignacio Vazquez-Abrams Jan 7 '16 at 21:09
  • 7
    \$\begingroup\$ Floating point is always an approximation only. Just like you can not write all digits of 1/3 in the decimal way. docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html for more in depth information. \$\endgroup\$ – PlasmaHH Jan 7 '16 at 21:11
  • 7
    \$\begingroup\$ Its not restricted to the STM32, you will see this behavior on any machine you try this code on. \$\endgroup\$ – brhans Jan 7 '16 at 21:21
  • 1
    \$\begingroup\$ See also just about every paper on Prof Kahan's page... cs.berkeley.edu/~wkahan ... including www.cs.berkeley.edu/~wkahan/JAVAhurt.pdf \$\endgroup\$ – Brian Drummond Jan 7 '16 at 21:24
5
\$\begingroup\$

Floating point numbers are represented as a sum of binary fractions times a power of two. In single-precision IEEE-754 floating point numbers, the fractions range from \$1/{2^0}\$ to \$1/2^{23}\$ and the powers of two range from \$2^{-126}\$ to \$2^{127}\$. For example:

$$12 = \left(\frac 1 1 + \frac 1 2\right) \cdot 2^3 = 1.5 \cdot 8 = 12$$ $$0.5 = \left(\frac 1 1\right) \cdot 2^{-1} = 1 \cdot \frac 1 2 = 0.5$$ $$12.5 = \left(\frac 1 1 + \frac 1 2 + \frac 1 {16}\right) \cdot 2^3 = 1.5625 \cdot 8 = 12.5$$ $$62 = \left(\frac 1 1 + \frac 1 2 + \frac 1 4 + \frac 1 8 + \frac 1 {16}\right) \cdot 2^5 = 1.9375 \cdot 32 = 62$$

But 0.06 and 0.8 cannot be evenly represented in this way, just as \$1/3\$ cannot be represented as a sum of decimal fractions:

$$\frac 1 3 \approx \left(\frac 3 {10} + \frac 3 {100} + \frac 3 {1000} + \frac 3 {10000} + \cdots \right) \approx 0.33333\dots$$

$$0.06 \approx \left(\frac 1 1 + \frac 1 2 + \frac 1 4 + \frac 1 8 + \frac 1 {32} + \frac 1 {128} + \frac 1 {256} + \frac 1 {512} + \frac 1 {16384} + \frac 1 {65536} + \frac 1 {1048576} + \frac 1 {2097152} + \frac 1 {4194304} + \frac 1 {8388608}\right) \cdot 2^{-5} \approx 1.91999995708465576171875 \cdot \frac 1 {32} \approx 0.0599999986588954925537109375$$

The decimal representations may vary from system to system. I'm not going to spend a lot of time writing out more examples, because hopefully you get the idea by now. The important thing is that floating point numbers are binary rational numbers, even though you put decimal rational numbers into your source code!

Floating point numbers are intended to be approximate. If you need to represent exact values, use integers or another alternative. If you don't know whether you need floating point, you probably don't.

For more information, I've found the Floating Point Guide to be easier to read than Goldberg's essay, but both are valuable.

\$\endgroup\$
3
\$\begingroup\$

The comments have covered the basics, but for a complete understanding of what's going on, I recommend You're Going To Have To Think! which is a series of articles.

In short, floats/doubles are approximations because they have to squeeze an infinite amount of values into a fixed size representation, so there will always be approximation errors. To not get surprised by how and when those errors appear, you need to understand completely how floats work.

Not only do the articles do a good job of describing the principles, but it opens with a great intro:

The dragon of numerical error is not often roused from his slumber, but if incautiously approached he will occasionally inflict catastrophic damage upon the unwary programmer's calculations.

So much so that some programmers, having chanced upon him in the forests of IEEE 754 floating point arithmetic, advise their fellows against travelling in that fair land.

In this series of articles we shall explore the world of numerical computing, contrasting floating point arithmetic with some of the techniques that have been proposed as safer replacements for it. We shall learn that the dragon's territory is far reaching indeed and that in general we must tread carefully if we fear his devastating attention.

\$\endgroup\$
2
\$\begingroup\$

There's an app note from STMicroelectronics, "AN4044 Application note Using floating-point unit (FPU) with STM32F405/07xx and STM32F415/417xx microcontrollers". I don't believe your STM32F103C8T6 has a hardware floating point unit (FPU), but its still interesting reading.

All ARM processors, as well as practically every other current microprocessor/microcontroller use a standard called IEEE 754-1985 to represent floating point numbers. It was adopted in 1985 and its first use in a hardware FPU was the 8087 chip from Intel, which was a companion to the 8086. (The original standard has actually been superseded by a newer one called IEEE-2008, which is backwards compatible.)

The two principal formats int IEEE standard are 32 bit (called float in the C language), and 64 bit (called double in C):

enter image description here

(This figure is adapted from the App note I referenced earlier.)

Prior to 1985, the representation of floating point numbers varied from one manufacturer to another, for example the IBM 360 mainframes introduced in the 1960's had a similar format, but the exponent in the single precision floating point representation was 7 bits instead of 8, and the unsigned integer part was 24 bits instead of 23. So it would of had one more bit of resolution, but only a exponent range of -62,+63 instead of -126,+127. The Digital Equipment VAX-11 format was really bizarre.

Others have included a bunch of math to show the imprecision of various floating point numbers, but just on an intuitive basis, if you look at just the 23 bit integer portion of a float, plus the 1 bit sign, and a corresponding 24 bit signed integer which has a maximum value of ±10\$^{23}\$, or ±8388608, and line up that value with one of your single precision floats,

    8388608
 12.5600004

you will see that the precision is about the same. I'm sure all the math pundits are groaning at this point, I'm just try to show how to look at this from a 50,000 foot level. Anyway it helped me visualize it.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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