When is 4.0 equal to 3?

I’m really struggling with this and i’m ready to throw my chip away for being bad at maths.
Any help or ideas or even some laughter would cheer me up.
I’ve written a VERY simple algorithm to convert 9 binary bits to a decimal. The algo works fine when the output is printed as a float or double, but when converted or cast as an int it tells me that 4.0 is 3.
The input comes from a dipswitch and is used to set the DMX address of a lighting fixture.

float decimalValue = 0.0;
      for (int i=0;i<9;i++)
      {
        decimalValue += (pow(2,i) * (digitalRead(bitToPinNumber(i))));  
      }
     //  decimalValue = 4.0;
      Serial.println(decimalValue);
      dmxAddress = int(decimalValue);   
      Serial.println(dmxAddress);

So here when decimalValue gets printed as 4.0
… then dmxAddress gets printed as 3

when decimalValue gets printed as 8.0
… then dmxAddress gets printed as 7

when decimalValue gets printed as 16.0
… then dmxAddress gets printed as 15

only when decimalValue < 4.0 does it perform the correct type cast.

I uncomment the decimalValue = 4.0 and then dmxAddress gets printed as 4. Which is nice of it.

Am i being a moron? Is this a compiler bug? Did my drink get spiked?
I have tested this on gcc compiler and the MS VC++ compiler with no unexpected results.

I don’t know what else to say…??? ideas?

Why do you convert it to float in the first place? That is about the most wasteful way to do it, with no benefit.

Most likely, the number is ending up as 3.9999, or similar, which will be rounded down when converted to the integer value it should have been all along…

Regards,
Ray L.

Don't use pow(). Its results are just inaccurate enough to cause the kind of trouble you are having. Try this instead:

int decimalValue = 0;
      for (int i=8;i>=0;i--)
      {
        decimalValue *= 2;
        decimalValue += digitalRead(bitToPinNumber(i));        
      }

To see what’s wrong directly, Serial.print() variable decimalValue with more precision. In my tests, it shows up clearly with seven digits after the decimal:

    Serial.print(decimalValue, 7);

As suggested above, the results of the floating point calculation wind up very close to, but a bit less than, the integers you expected. Converting to integer doesn’t round the result; it truncates it, discarding everything after the decimal point.

You might do well to presume that every floating point calculation will be a little bit off. You’d be right a lot of the time.

If dmxAddress is indeed a DMX address, then it’s an integer. Calculating it with floating point adds no functionality, and can introduce error, just like it does in your sketch. To avoid this problem, I’ll recommend that you use integer multiplication as described above, or use the shift operator to generate integer representations of powers of two. If you just have to use floating point, then round the result by adding 0.5 before converting to integer.

4.0 is equal to 3 when it’s actually 3.99999 and you round it down.

Learn about C++ bit operators (<<,>>,|,&,^) and what they do.

Thanks very much for all the replies. Why when I serial.print a floating point number that is actually 3.99999 does it not print 3.99999 and prints 4.0 instead? Why should it lie to me? The problem doesn't occur on my Intel machine compilers because it has hardware floating point arithmetic in the ALU then I guess. But if I output a variable that is 3.99999 then that is what it would always print making debugging easy. I know the arduino is horrific bad with floating point but serial.print really didn't help me solve this at all. Many thanks again, problem spotted by forcing serial.print to tell the truth and fixed by using integer arithmetic. Cheers!

Don’t use pow(). Its results are just inaccurate enough to cause the kind of trouble you are having.

Wrong. The results are quite accurate, given the limits of the hardware. It’s the wrong function to compute 2n, which is why it should be avoided, not because it is inaccurate.

PaulS:
Wrong. The results are quite accurate, given the limits of the hardware. It’s the wrong function to compute 2n, which is why it should be avoided, not because it is inaccurate.

I said "just inaccurate enough to cause the kind of trouble [the OP is] having", not "inaccurate". I’m not going to get into some kind of philosophical discussion about just how much accuracy a thing must possess in order to qualify as “accurate”. Instead, I’m going to be pragmatic and say that the accuracy is, for this particular use, insufficient, as is demonstrated by the trouble the OP was having.

neusinger2008: Thanks very much for all the replies. Why when I serial.print a floating point number that is actually 3.99999 does it not print 3.99999 and prints 4.0 instead? Why should it lie to me?

It's not lying, it is rounding, as it is designed to do. The default is two decimal places and it will round to that.

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  float f = 3.99999999;
  Serial.println (f);
  }  // end of setup

void loop ()
  {
  }  // end of loop

That prints:

4.00

[quote author=Nick Gammon link=msg=2398606 date=1442359436] That prints:

4.00

[/quote]

It prints 4.00 because it is 4.00. See here: http://www.h-schmidt.net/FloatConverter/IEEE754.html

Yes, but I thought I wrote:

  float f = 3.99999999;

Next you'll be telling me that:

0.99999999999999999999 ...

... is the same as 1

[quote author=Nick Gammon link=msg=2398704 date=1442371484] Yes, but I thought I wrote:

  float f = 3.99999999;

[/quote]

You did. Now try putting that number into the floating point demo at the link I gave you, and see what you get.

The real question is not how does that webpage do the conversion from string to float, but how does the COMPILER do the conversion. Has anyone actually RUN that code? What comes out will depend on whether, and how, the compiler rounds the "extra" digits beyond the 7(-ish) significant digits a float can support.

Regards, Ray L.

odometer: Now try putting that number into the floating point demo at the link I gave you, and see what you get.

Yep, I did that.

However if I put in "3.999999" I get that back.

I realize a float has around 7.2 digits of precision. I'm just pointing out that for someone not used to the way that floats work, it seems odd to put in "3.9999999" and not get it back again.

None of this matters.

My rule of thumb is: always treat a float value as being an approximation of some quantity. They are useful for things that are never really accurate anyway (sensor readings of physical quantities), statistical averages and the like, and mathematical things that have no exact representation (square roots, trig, Pi).

For anything that needs to be computed precisely, keep an integer value of the smallest unit - seconds, cents, whatever.

My other rule of thumb is: any variable that is a measure of some physical quantity (or function that returns some physical quantity) should be suffixed with the unit of measurement. Been a lot of time and money wasted over the years on variables holding minutes rather than seconds, or feet rather than meters.

If I was code nazi of the world, I would enforce this rule ruthlessly.

Code Nazi.jpg

Every text I've ever seen that addresses floating point calculations has warned about expecting their results to be absolutely precise. The Wikipedia article on floating point says,

The fact that floating-point numbers cannot precisely represent all real numbers, and that floating-point operations cannot precisely represent true arithmetic operations, leads to many surprising situations.

And,

Conversions to integer are not intuitive ... Floor and ceiling functions may produce answers which are off by one from the intuitively expected value.

The particular issue raised by the OP is one of a couple of classic examples of the pitfalls of floating-point and calculations, regardless of the platform. Another is testing floating-point results for strict equality.

As I recall the only test for equality that is guaranteed to work is comparison to zero.