Trying to improve sprintf not working with %f , help please

i try some solution of my own to be able to print float with sprintf

but no success so far , maybe you can have a look at the code and explain what i did wrong

i consider myself an advanced beginner but definitly not an expert

Thanks

edit : the only thing i am sure about is that on mega or uno , sprintf do not deal with float (%f)

float f1 = 1.23456;
float f2 = 2.34567;
float f3 = 3.45678;
float f4 = 4.56789;
float f5 = 5.67891;



char buffer1[200];
static char buffer2[10];  // if static and global , should always be at the same address ?? or maybe not ?
                          // also don t need to use free();   ,  am i wrong ?

char* myftostr(float data, byte width, byte precision) {
  //static char chararray[15];
  dtostrf(data, width, precision, buffer2);
  return buffer2;  // return pointer of buffer2
}

void setup() {
  Serial.begin(115200);
}


// trying to "fix" sprintf "%f" disabled on arduino

void loop() {

Serial.println("BEGIN PRINT \"NORMAL\"");
Serial.print("float 1 val : ");
dtostrf(f1, 5, 1, buffer2);
Serial.print(buffer2);
Serial.print(" float 2 val : ");
dtostrf(f2, 10, 2, buffer2);
Serial.print(buffer2);
Serial.print(" float 3 val : ");
dtostrf(f3, 4, 4, buffer2);
Serial.print(buffer2);
Serial.print(" float 4 val : ");
dtostrf(f4, 1, 5, buffer2);
Serial.print(buffer2);
Serial.print(" float 5 val : ");
dtostrf(f5, 1, 0, buffer2);
Serial.println(buffer2);


Serial.println("BEGIN PRINT with function");
Serial.print("float 1 val : ");
Serial.print(myftostr(f1, 5, 1));    // CALL myftostr and print return pointer positioned at buffer2
Serial.print(" float 2 val : ");
Serial.print(myftostr(f2, 10, 2));
Serial.print(" float 3 val : ");
Serial.print(myftostr(f3, 4, 4));
Serial.print(" float 4 val : ");
Serial.print(myftostr(f4, 1, 5));
Serial.print(" float 5 val : ");
Serial.println(myftostr(f5, 1, 0));


Serial.println("BEGIN SPRINTF with function");
sprintf(buffer1, "float 1 val : %s float 2 val : %s float 3 val : %s float 4 val : %s float 5 val : %s",
              myftostr(f1, 5, 1), myftostr(f2, 10, 2), myftostr(f3, 4, 4), myftostr(f4, 1, 5), myftostr(f5, 1, 0));
              // line splitted for visibility , change nothing
Serial.println(buffer1);


Serial.println("BEGIN SPRINTF with function and sprintfstring separated");
static const char strSprnt[] = { "float 1 val : %s float 2 val : %s float 3 val : %s float 4 val : %s float 5 val : %s" };
sprintf(buffer1, strSprnt, myftostr(f1, 5, 1), myftostr(f2, 10, 2), myftostr(f3, 4, 4), myftostr(f4, 1, 5), myftostr(f5, 1, 0));
Serial.println(buffer1);




delay(2500);

}


// output on serial monitor
//
//BEGIN PRINT "NORMAL"
//float 1 val :   1.2 float 2 val :       2.35 float 3 val : 3.4568 float 4 val : 4.56789 float 5 val : 6
//BEGIN PRINT with function
//float 1 val :   1.2 float 2 val :       2.35 float 3 val : 3.4568 float 4 val : 4.56789 float 5 val : 6
//BEGIN SPRINTF with function
//float 1 val :   1.2 float 2 val :   1.2 float 3 val :   1.2 float 4 val :   1.2 float 5 val :   1.2
//BEGIN SPRINTF with function and sprintfstring separated
//float 1 val :   1.2 float 2 val :   1.2 float 3 val :   1.2 float 4 val :   1.2 float 5 val :   1.2

which arduino ?

mega or uno

When Arduino started with the ATmega8, the floating point software for sprintf() was removed (or not included), because there was so little memory available.
That is why you can't have floating point in 2024 with sprintf() on the Uno or Mega. No fun for you.

Under Linux, my solution is to simply alter the link options in platform.txt to include float support.

Locate this line:

compiler.c.elf.flags={compiler.warning_flags} -Os -g -flto -fuse-linker-plugin -Wl,--gc-sections

and add -Wl,-u,vfprintf -lprintf_flt -lm to the end so it looks like this

compiler.c.elf.flags={compiler.warning_flags} -Os -g -flto -fuse-linker-plugin -Wl,--gc-sections -Wl,-u,vfprintf -lprintf_flt -lm

Since not every sketch needs float support (and sometimes they need the extra code space that the float library would take up), I actually have platform.txt.nofloat (the original file) and platform.txt.float and a couple of scripts to switch a soft link between them.

avrnofloat

#!/bin/bash
cd ~/.arduino15/packages/arduino/hardware/avr/1.8.3
/bin/rm platform.txt
ln -s platform.txt.nofloat platform.txt

avrfloat

#!/bin/bash
cd ~/.arduino15/packages/arduino/hardware/avr/1.8.3
/bin/rm platform.txt
ln -s platform.txt.float platform.txt
1 Like

i am passing C string to sprintf .... nothing to do with float

it is just that my function is always returning the value from the first call when called multiple time inside the sprintf statement.

but when i call it multiple time with Serial.print(Myfunction); , it work perfectly fine

that's a solution , but it was also for learning purpose to understand why my function is only kind of half working

If you still want help, please explain what "half working" means. Describe what you expected to happen, and what happened instead.

You return always the same pointer to the c-string so the compiler is evaluating the arguments and they come back as the same pointer to whatever string was built last

i expect that my multiple call to myftostr inside sprintf to give me the different result

to print the same line as in the exemple above named "BEGIN PRINT with function"

but it only repeat the first result , aka 1.2

yes , but the content located at the pointer is supposed to have been updated by the call to "dtostrf" inside the function ??

thats the part i dont understand here

You are missing may be how the compiler works

When you call

The compiler evaluates first all the parameters and then calls the sprintf() function. And here all the parameters are exactly the same pointer to the same region in memory which holds the text from the last evaluation of the parameters. There is no copy made of the data pointed by the pointer.

Another way to say this, if you do

char * ptr1 = myftostr(f1, 5, 1);
char * ptr2 = myftostr(f2, 10, 2);
char * ptr3 = myftostr(f3, 4, 4);
char * ptr4 = myftostr(f4, 1, 5);
char * ptr5 = myftostr(f5, 1, 0);

All the ptrX are the same as buffer, the global variable. And so if you try to print each pointer’s content it will be whatever was generated by the last call to myftostr()

You would need to use multiple strcat() commands to build up buffer1

i am still strugling with pointer black magic sometimes , i had to read that many time slowly :sweat_smile:

i was thinking that sprintf was runing , then evaluating each parameter sequentially

i understand the issue with this exemple , thanks

i was trying to make the code more readable with fewer line , many call to strcat would not change that too much

and learning new things in the process , but i can also keep the simplest serial.print from the first or second exemple

by the result of "1.2" repeating , the evaluation of the parameters seem to be backward right ?

the C++ language specification does not guarantee the order of evaluation of the parameters, so you should not count on this.

The typical way functions needing c-string work is that the caller is passing the buffer in which you want the c-string to be returned. This way you don't get something that is the same for every call.

for example

float f1 = 1.23456;
float f2 = 2.34567;
float f3 = 3.45678;

char* myftostr(char * destination, const size_t destinationSize, float data, byte width, byte precision) {
  // in an ideal world you would use destinationSize to ensure you don't overflow the buffer
  // left as an exercice for the reader
  dtostrf(data, width, precision, destination);
  return destination;
}

void setup() {
  Serial.begin(115200);

  char result[200];
  char b1[20], b2[20], b3[20];
  sprintf(result, "float 1: [%s], float 2: [%s], float 3: [%s]", // would be better with snprintf
          myftostr(b1, sizeof b1, f1, 5, 1),
          myftostr(b2, sizeof b2, f2, 10, 2),
          myftostr(b3, sizeof b3, f3, 4, 4));
  Serial.println(result);
}

void loop() {}

The issue with the dtostrf() function is that you don't know how large the buffer needs to be and so you can overflow. In an ideal world you would check how many digits are needed for the integral part of the float and use that with the precision you need and take into account the possible negative sign and dot to ensure the buffer is large enough before actually calling dtostrf().

also you should use snprintf() instead of sprintf() to ensure you don't overflow the result buffer

1 Like

thanks for your time , this all make sense now

in this particular case , the float come from sensor and can NEVER be more than the used value

same for length of sprintf output , this can NEVER be bigger

good
have fun !