Extract value from string gives wrong value

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

what Arduino are you using?
typically floats are four bytes and have 6 or 7 significant digits - printing more than that is meaningless

Hi Horace,

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.

You are aware that both values use eight digits, not the claimed three.

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.

You still don't understand the float limit.

Only print() specifies decimals behind the dot,
the 6-7 digit limit for floats includes all digits.

Only the first six to seven digits can be used.

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.

char myData[] = "1-0:1.8.1(015323.748kWh)" ;

void setup()
{
  Serial.begin(9600);
  char *token;
  token = strtok(myData, "(");
  //--------------------------------------
  token = strtok(NULL, ".");
  int y1 = atoi(token);
  //--------------------------
  token = strtok(NULL, "k");
  int y2 = atoi(token);
  //-----------------------
  Serial.print(y1, DEC);
  Serial.print('.');
  Serial.println(y2, DEC); //shows: 15323.748
}

void loop(){}

IEEE754Float
Figure-1:

have a look at Characteristics of floating-point types
if run the following program on a PC using GNU C/C++

#include <float.h>
#include <math.h>
int main(void) {
    float x=15323.748f;
    printf("sizeof(float) %d\n", sizeof(float));
    printf("FLT_DIG %d\n", FLT_DIG);
    printf("x = %f\n", x);
    printf("sizeof(doble) %d\n", sizeof(double));
    printf("FLT_DIG %d\n", DBL_DIG);
    printf("PI      3.1415926535897932384626433\n");
    printf("M_PI %25.20lf\n", M_PI);
}

I get

sizeof(float) 4
FLT_DIG 6
x = 15323.748047
sizeof(doble) 8
FLT_DIG 15
PI      3.1415926535897932384626433
M_PI    3.14159265358979310000

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?

conversion from float binary to text representation again is an approximation

it depends on what you data is
if it is real floating numbers from an experiment it depends on the precision of your input data

  1. if your data has 14 or 15 significant digits use double
  2. if you data has 2 or 3 significant digits (e,g, from an 8-bit ADC) use float - double would waste memory and take extra time

Above binray32 representation for 15323.748 , when converted to numerical value, we get: 15323.749 which is inaccurate.

  float x2;
  long *ptr1;
  ptr1 = (long*)&x2;
  
  *ptr1 = 0x466F6EFE;
  Serial.println(x2, 3); //shows: 15323.749

Which is not available on:

So, @kees_arduino will first need to switch to an ARM or ESP based processor board.

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.

Kind regards