Problem comparing two float type numbers

Hi all,

I'm using an Arduino Mega (ATmega2560) and I'm facing a very weird issue when I compare two float numbers, which seem to have the same value.
My code is below. Please don't mind those conversions I made in the code. It is used in my actual application where I first detected this problem.

char a[]={'0','0','0','0','0','0','0','3'};
char d[]={'0','0','0','0','0','0','0','5'};
float b=0;
volatile float c[]={0.01, 0.01, 0.01};
volatile long int p=299;
volatile long int k=499;
volatile float t=0;

void setup() {
  Serial.begin(9600);
  b=atof(a);
  t=p*c[0];
  Serial.print("b=");
  Serial.println(b,3);
  Serial.print("t=");
  Serial.println(t,3);
  Serial.print("diff1=");
  Serial.println(b-t,3);
  if (b-t<c[0])
  {
    Serial.println("NOT Correct!!!");
  }
  else
  {
    Serial.println("Correct!!!");
  }

  b=atof(d);
  t=k*c[0];
  Serial.print("b=");
  Serial.println(b,3);
  Serial.print("t=");
  Serial.println(t,3);
  Serial.print("diff2=");
  Serial.println(b-t,3);
  if (b-t<c[0])
  {
    Serial.println("NOT Correct!!!");
  }
  else
  {
    Serial.println("Correct!!!");
  }
}

void loop() 
{

}

As you can see the value b-t is equal to 0.01, and the value of c[0] is also 0.01, however the first "if" statement returns "NOT Correct!!!", meaning that the value of b-t is less than c[0].

On the second option of comparing I changed the value from 3 to 5. And in this case again the value b-t is equal to 0.01, and the value of c[0] is also 0.01, and the second "if" statement returns "Correct!!!".

Can anyone tell what is the reason of this behavior?

Thanks in advance.

You've got more problems than just comparing two values.
Start by looking at the documentation for atof.

gevorgparsyan:
As you can see the value b-t is equal to 0.01

No, I can see that " b=atof(a);" is PURE NONSENSE.

The function atof() works on nullterminated strings only.

But in your declaration, array a is an array of chars, terminated with a '3' character.

Hey Jurs,

I have added the null terminations, but the problem still exists.

char a[8]={'0','0','0','0','0','0','3','\0'};
char d[8]={'0','0','0','0','0','0','5','\0'};
float b=0;
volatile float c[]={0.01, 0.01, 0.01};
volatile long int p=299;
volatile long int k=499;
volatile float t=0;
void setup() {
  Serial.begin(9600);
  b=atof(a);
  t=p*c[0];
  Serial.print("b=");
  Serial.println(b,3);
  Serial.print("t=");
  Serial.println(t,3);
  Serial.print("diff1=");
  Serial.println(b-t,3);
  if (b-t<c[0])
  {
    Serial.println("NOT Correct!!!");
  }
  else
  {
    Serial.println("Correct!!!");
  }

  b=atof(d);
  t=k*c[0];
  Serial.print("b=");
  Serial.println(b,3);
  Serial.print("t=");
  Serial.println(t,3);
  Serial.print("diff2=");
  Serial.println(b-t,3);
  if (b-t<c[0])
  {
    Serial.println("NOT Correct!!!");
  }
  else
  {
    Serial.println("Correct!!!");
  }
}

void loop() 
{

}

What else am I doing wrong?

Using == on floats is a fools game - don't ever doit!

Mark

Hey Mark,

But I have not used ==. Can you please clarify what you meant?

thanks

That's what you get if you do float calculation and expect precise results... Why do you do float calculations in the first place?

Small sketch to illustrate the problem:

#define ALL(x) printAll(#x,x)

float b = 3;
float c = 0.01;
int p = 299;


void setup() {
  Serial.begin(115200);
  float t = p * c;
  ALL(b);
  ALL(t);
  ALL(c);
  float diff = b - t;
  ALL(diff);
  
}

void loop() {
  
}

void printAll(char* letter, float & val){
  Serial.print(letter);
  Serial.print("=\t");
  Serial.print(val, 10);
  Serial.print(" (0x");
  Serial.print(*((unsigned long*)&val), HEX);
  Serial.println(")");
}

In the () the hex value of the float.

Hi septillion,

Thanks for the example. What alternative can I use if I need to have floating point numbers?

Let me rephrase that:
What alternative can I use if I need to have floating point numbers decimal points?

Float != decimal point!

A float is made so it can hold very small and very large number without taking a massive amount of RAM but with the trade off of precision. If you want precision (and on a 8-bitter speed as well) stay away from it.

Now you know that, lets assume you make a system that need to track money. You could use a float for it but knowing the above it might not be great is you loose or gain money in the calculation... But yeah, you want to work with cents as well. Why not simply store all values in cents? Now for the storage and calculation you have no decimal point but as long as you know you work with cents all the calculations are correct. And when you want to print it in €, $ or whatever you just add the decimal point.

int value 567;
Serial.print(value / 100);
Serial.write(','); // right decimal for Europeans :p
Serial.print(value % 100);

It's called fixed point. Aka, just save everything orders of magnitude bigger until you have no decimal point left to save.

gevorgparsyan:
What else am I doing wrong?

You are assuming that float numbers have an unlimited accuracy.
This is wrong!

In fact, the 'float' data type is limited to 6-7 decimal digits only!

So maybe these three float numbers
0.01
0.01000001 and
0.009999999 might have the same float representation, or maybe not.

Converting float to decimal representation and decimal representation to float is always an action with limited accuracy.
Accuracy 6-7 significant digits in case of float.

This is caused by the fact, that a 32-bit binary()! representation of a number (which is 'float'), cannot have an exact decimal representation with unlimited decimal accuracy!

binary 'float' to decimal conversion and the other way round is only accurate to 6-7 significant decimal digits.

Hi,
Normally it is not good practices to compare floating point numbers. What I do multiply the floating numbers by 100 before comparing it.
example:

float num1 = 1000.12345;
float num2 = 1959.12347;
long num3 ;
long num4 ;

num3 = num1 *100; >>>>num3 = 100012
num4 = num2 *100; >>>>num4 = 195912

Now you can do the comparison.
if (num3>num4){do something;};

Maybe there are a better solutions but its worked for me.

Here is where I say that "Working with floating point is like moving sand piles, Every time you move it you lose a little sand and pick up a little dirt."

C programming faq: testing floats for equality

tauro0221:
Hi,
Normally it is not good practices to compare floating point numbers.

. . . for equality

No, you don't fix that by multiplying it by 100. That will only introduce a new decrease in accuracy.

Fix in this case is simply to not use float. :slight_smile:

0.01 cannot be precisely represented as a floating point number because 5 and 2 are relatively prime. Floating point numbers only store values raised or lowered to some power of 2. 1234/512 can be represented precisely, for instance. 1/3 cannot be.

If you put 0.01 in your code, your compiler will substitute in a floating point value whose value is as close to that as possible. But that's not the same thing as exactly that value.

If you need precision, used integer arithmentic. For instance, if you need "number of hours" accurate to the second, don't store a floating-point number of hours, store a long integer number-of-seconds.

If you are working with physical measurements, or mathematical artifacts, floating point is fine. But remember that the values are always a tiny bit "fuzzy" - they have a tolerance, and need to be treated that way.

gevorgparsyan:
What alternative can I use if I need to have floating point numbers?

What Paul says above about picturing float values as being a bit fuzzy is a very good way to think about it.

For an example of why this (often) doesn't matter, say we had a float "x" and we wanted to choose different algorithms based on whether or not x was less than 1. Perhaps one algorithm was known to work better for x<1 and the other work better for x>1. In a case like this however, where x represents a continuous real world variable, it's a reasonable assumption that both algorithms are about the same when x=1. So realistically, it probably doesn't matter which algorithm we choose when x is very close to 1.0.

So in many cases like the above, the "fuzziness" of floats simply doesn't matter. And if it does matter, then it probably means that a float (at least of the precision you are using) is not appropriate for the problem at hand.

If you want some concrete examples of how we deal with this fuzziness in practice, the answer is that we usually use a "tolerance" constant that is appropriate for the precision we are using. For example:

#define toln 1e-6 // Thanks AWOL. :wink:

In the following examples I'll assume we are comparing "float x" with 1.0.

For testing equality we would typically use:

if ( fabs(x-1.0) < toln ) ...

When testing inequality (for example less than) you could use either,

if (x < 1.0) ...
or
if (x <= 1.0)...

To be honest, it really shouldn't matter which of the above two that you use, at least if a float is truly appropriate for the problem at hand. In either case we don't really care what happens for values very close to 1.0. We simply accept that for calculations that would theoretically give 0.9999999 to 1.0000001, that it could go either way.

If for some reason we absolutely must have x=1 excluded, then use something like:

if (x + toln < 1.0) ...

If for some reason we absolutely must have x=1 included, then use something like:

if (x <= 1.0 + toln) ...

BTW. The choice for toln could depend upon just how many chained calculations (and how many opportunities there are for rounding errors) before you get to your comparison. But typically it would be somewhere about 1E-6 for single precision and somewhere about 1e-13 for double precision.

stuart0:
For example:

#define toln = 1e-6

Oops

AWOL:
Oops

Yes, I'm from a very long time Pascal/Delphi background. I still make tons of little syntax errors like that every time I touch C/C++ :-* . Fortunately though, the compiler finds most of them for me pretty quickly. :slight_smile:

Thank you all for your comments and solutions.
stuart0, I guess the solution you offered is the simplest way to overcome this issue (of course taking into account the tolerance I will have in the values).

stuart0, I guess the solution you offered is the simplest way to overcome this issue

No use the code given in the link in reply #11

Mark