I am trying to extract a value from a string, but the value is almost equal to the string, but not in total.
I have a string "1-0:1.8.1(015323.748kWh)" and I need the value 15323.748, to extract the string value I use sscanf("1-0:1.8.1(%ld.%ld", int_prt , dec_prt ), int_prt will be 15323 and dec_prt will be 748. So far so good. Next I will divide 748 with 1000 so I get 0,748 and last but not least I will add both so I would expect 15323.748, wrong this time I get 15323.749 when I print 3 decimal places. In fact when I show with 9 decimal places I get 15323.748046875, where the h..ll does thus 46875 come from?
I checked dec_prt with 9 decimals 0,7480000048. I suspect that this has someting to do with implicit conversion, but I am relatively new to Arduino.
Here is the code
long int_part = 0;
long dec_part = 0;
float power = 0;
float dec_part_float;
//char inp_str[] = "1-0:1.8.1(014920.091*kWh)";
char inp_str[] = "1-0:1.8.1(015323.748*kWh)";
char src_str[] = "1-0:1.8.1(%ld.%ld*";
// syntax of sscanf_res = sscanf(inp_str, "1-0:1.8.1(%ld.%ld*", &int_part, &dec_part);
// read this as follows: read after the string "1-0:1.8.1(" %ld = long decimal till ".", read next a decimal till "*" and store both subsequently in bot variables
int sscanf_res = 0;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}
void loop() {
power = det_float (inp_str, src_str);
Serial.println(power,9);
delay(6000);
}
float det_float(char *inp, char *src) {
float float_res; // Float result
float dec_part_flt; // decimal part of the value float
long int_prt; // Integer part
long dec_prt; // decimal part of the value long
sscanf_res = sscanf(inp, src, &int_prt, &dec_prt);
// inp = "1-0:1.8.1(015323.748*kWh)" src = "1-0:1.8.1(%ld.%ld*"
dec_part_flt = dec_prt;
// 748
dec_part_flt = dec_part_flt / 1000;
// 0.748
float_res = int_prt;
// 015323
float_res = float_res + dec_part_flt;
// 015323 + 0.748 is 15323.748 float_res
return float_res;
}
The result with 9 decimals
15323.748046875
I am using arduino IDE 2.0.4 and a MEGA (not a clone). The meaning that I print 9 decimals is to check why 15323.749 is printed when I expect 15323.748. I will only need 3 digits, later on this will be sent to a database LAMP. The OS is Ubuntu 22.04.
base10 numeric values, e.g. 15323.748, entered as float are converted to a binary representation which is an approximation to the base10 value - have a look at floating point precision
if floating point preceision is 6 or 7 significant digits the final digit of 15323.748 will be an approximation
you can use double which is typically eight bytes and can repreent 15/16 significant digits
Hi Whandall, I am not sure of that, but I was sure it was more that 3 decimals, but the question remains why is the actual value 15323.748046875, if it is 8 decimals it displays 15323.74804687, I would expect 15323.74800000.
Floats are not really an accurate representation as you are finding out. Definitely don't use these for currency calculations.
Why not change the units to watt hours instead of kWh so you are always working with an integer value which can be exactly represented ? Alternatively, ignore the small error.
If you want to display exactly 15323.748, the following sketch may help; otherwise, you will always get/see 15323.749 due to the fact that floating point arithmetic is not exact and loses accuracy owing to inter-conversion. A typical example of encoding/representation of floating point number is shown in Fig-1 below.
Ok did some small recap on mathematics, long time ago. If I understand this correctly, the value 15323.748 is converted to a binary floating point losing some accuracy and serial.print converts the binary float back again to value which is accurate.
S Exponent Mantissa
0 10001100 11011110110111011111110
S = plus sign
So I need another type not equal to float? or just use Watt as 6v6gt suggested. What type can I use?
Are you needing to do any calculations with the number, or are you just printing it? If there are no calculations, then do something like the code in post #9 (be careful of leading zeros in the decimal part), or just use the ASCII directly from the string with no conversion to numeric values. If calculations are needed, it might also be possible to do that with integers, as long as you keep track of the decimal location.
Ok, thanks very much for all replies, I learned a lot in short time. At this stage I will not accept loss of precision, maybe later when I exactly know the maximum deviation. So the most accurate way is to use a smaller dimension in this case W which is 1000 kW, so the value will be 15323748 the maximum number of digits of the meter is 9 max so a long or unsigned long will dot the job.