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.
1 Answer 1
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:
- The numbers you use are not exactly representable as floating point numbers.
- The
double
on an Arduino Uno is actually equivalent to afloat
(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.
-
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– WildCat2020年12月08日 17:29:58 +00:00Commented Dec 8, 2020 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 withround(1e4*x)
. Afloat
is worth about 7 decimal digits (FLT_EPSILON
≈1.19e-7
).Edgar Bonet– Edgar Bonet2020年12月08日 20:41:40 +00:00Commented Dec 8, 2020 at 20:41