4

I am writing (in C) some low level digit manipulation routines that convert int to float and vice versa for Arduino. I came across some puzzling behaviour with Arduino rounding some numbers but not others. So I located and extracted the offending code into a simple program which I compiled on desktop Linux PC with GCC and on Arduino - they give different results. Why? Here are the programs. The goal is to take the float number 1.3140 and the float number 2.3140 and split them into two integers. In the first case I expect '1' and '3140' and in the second case I expect '2' and '3140' (the terminal '0' is important for my application). The Arduino code:

void setup() {
double dnum,x,y;
uint16_t intpart,fracpart;

Serial.begin(9600);

dnum = 1.3140;

 x=modf(dnum,&y);
 intpart = (uint16_t)y;
 fracpart = (uint16_t)(10000.0*x);

Serial.println(dnum,4);
Serial.println(intpart);
Serial.println(fracpart);

}

and the output I get is:

1.3140
1
3140

Fantastic! Success. But hold on. If I change the hard-coded number to dnum = 2.3140 I get this:

2.3140
2
3139

What? Why did it round the second part down? Am I doing something fundamentally wrong? So I wrote a little C program for my PC (compiled with gcc). Here is the code:

#include<stdint.h>
#include<stdio.h>
#include<math.h>

  
int main (int argc, char *argv[])
{
 double dnum,x,y;
 uint16_t intpart,fracpart;

 dnum = 2.3140;
 
  x=modf(dnum,&y);
  intpart = (uint16_t)y;
  fracpart = (uint16_t)(10000.0*x);
 
 printf("Int_part = %u\nFrac_Part = %u\n",intpart,fracpart);
 
 
 return 0;
}

When run I get this result:

Int_part = 2
Frac_Part = 3140

Now that's what I expect - and to be thorough I also did the above C program with dnum = 1.3140; and I again got the 'correct' expected result:

Int_part = 1
Frac_Part = 3140

So why does the Arduino round the fractional part down when I convert it to an integer sometimes and not others? I tried this on a genuine Arduino Uno, a clone Arduino uno and a clone Arduino nano and all give the same results. In 'theory' it could be some difference in the compiler tolerance to some coding impropriety I have done (gcc is more tolerant) but can anyone tell me what I did wrong and how to get the Arduino to behave (the way I want it to)? Thanks.

5

When you cast a floating point number to an integer, it always gets rounded towards zero. If you want round-to-nearest behavior, you should use the Arduino round() function.

As to the origin of the discrepancy you see, it comes from two facts:

  1. The numbers you use are not exactly representable as floating point numbers.
  2. The double on an Arduino Uno is actually equivalent to a float (32 bits only).

When you write a statement such as dnum = 1.3140;, the compiler stores in dnum the floating point number that is closest to the decimal number 1.3140.

Compare:

float32(1.3140) = 1.31400001049041748046875
float64(1.3140) = 1.31400000000000005684341886080801486968994140625
float32(2.3140) = 2.3139998912811279296875
float64(2.3140) = 2.31400000000000005684341886080801486968994140625

Out of these 4 examples, only float32(2.3140) is rounded down.

2
  • Very clear - thanks. I was unaware of the Arduino 32 bit limit but you educated me. I confirmed your answer also by making 'dnum' a float on my desktop PC program and it gives the same answers as my Arduino so practice and theory agree. I don't think round() will help me here, I just need to accept I can't get the precision I want on the Arduino (without additional 'arbitrary precision' code that will overflow my PROGMEM in my application).
    – WildCat
    Dec 8 '20 at 17:29
  • 1
    @WildCat: If you only need 1+4 decimal digits or so, like in the example you give, a float should suffice, provided you get the factional digits with round(1e4*x). A float is worth about 7 decimal digits (FLT_EPSILON1.19e-7). Dec 8 '20 at 20:41

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.