printf() with floats?

I know the arduino core libraries (maybe even the “std” libraries) are not 100% perfect, thus the printf() function can’t not handle float values. This is mainly due to va_start, va_list … va_end, not being the same as you would find if you use a real C/C++ compiler.

Well, I want to try adding float values to printf(), or at least give it a shot. However I can’t not find where the Arduino software developers hid the actual __builtin_va_start … end functions. Are they somewhere in the library folders or is it part of the GCC files?

I made this sketch to convert a floating value to a string. I can also change it to the standard format of “%4.2f” down the line, that’s not a problem. But where do I start?

/*
  Had to make my own pow() function as the standard pow() has a return type of double,
  and for some reason type casting from a double to an int produces 1 less that of the value. 
  Ex. int(pow(10, 3)) returns 999 instead of 1000. (using IDE 1.6.0 on an UNO)
*/

void setup() 
{
  // put your setup code here, to run once:
  Serial.begin(115200);

  char myB[10]; // buffer
  ftos(myB, 4, 3, 1234.567);
  Serial.println(myB);
}

void loop() {
  // put your main code here, to run repeatedly:
}

void ftos(char * buf, byte W, byte D, float data)
{
  long Wdata = data, tmp;
  float dec = data - long(data);
  
  // get the first whole number and convert it
  buf[0] = Wdata / Pow(10, W - 1) + '0';
  tmp = Wdata % Pow(10, W - 1);
  
  //now get the rest of the whole numbers
  for (byte i = 1; i < W; i++)
  {
    long factor = Pow(10, W - 1 - i);
    buf[i] = (tmp / factor) + '0';
    tmp %= factor;
  }
  
  buf[W] = '.'; // add the decimal point

  // now do the decimal numbers
  for (byte i = 0; i < D; i++)
  {
    dec *= 10; 
    buf[W + i + 1 ] = (long(dec) % 10) + '0';
  }
  // don't forget the NULL terminator
  buf[W + D + 1] = NULL; 
}

unsigned long Pow(long V, byte shift)
{
  unsigned long Val = 1;
  while(shift-- != 0)
    Val *= V;
  
  return Val;
}

Maybe this will help:

especially post #3.

Ok, that is awesome. Thank you Econjack

HazardsMind:
I know the arduino core libraries (maybe even the “std” libraries) are not 100% perfect, thus the printf() function can’t not handle float values. This is mainly due to va_start, va_list … va_end, not being the same as you would find if you use a real C/C++ compiler

Where did you get that idea? The Arduino compiler is just an AVR version of the popular GCC compiler. There is absolutely nothing wrong with it, and it certainly can handle floating point functions.

Problem is the people who developed the Arduino IDE decided to leave out the floating point support. It’s simply a matter of the IDE not generating the proper GCC compile and link strings to send to the compiler.

The reason the FP code is not linked in is because it takes up about 2K of space, and we don’t want people with a 32K microcontroller to write a 5K program and then use another 2.5K for floating point. No, sir! We have to keep as much flash memory unused as possible!

The IDE want from version 1.0 to 1.06 to what now? 1.6.5 and still the requests for floating point support (and printf_P support) have gone unheard.

It takes literally less than 10 minutes to write a few extra lines of code to add an OPTION in the IDE to use or not use floating point.

Can you imagine how much torn out hair would finally be growing back by now if people could just click [ ] Floating Point instead of wondering what the darn question mark means and discovering that it’s actually NOT their fault or a mistake in programming?

(sense my frustration you can, mmmmmm?) ← Yoda voice

Yes, but you can use dtostrf if you want to print floats. People who are already struggling with program size won't want the extra 2 KB included, if they don't even print floats ever.

The option idea might be one work-around, but then you get people complaining that the code you posted "doesn't work" because you have the option checked and they don't.

If you have 1.6.5 and above, you can download my PrintEx library from the library manager.
Or it can be manually installed in versions 1.5.x and up.

Among other things, it includes printf with floating point support.

#include <PrintEx.h>

PrintEx serial = Serial;

void setup(){
  Serial.begin(9600);
  serial.printf("/%31n\\\n| A floating point number: %f |\n\\%31n/", '=', 3.14f, '=' );
}

void loop() {}

Produces:

/===============================\
| A floating point number: 3.14 |
\===============================/

The readme has an overview of its usage.
https://github.com/Chris–A/PrintEx

Honestly… when has someone ever struggled with program size? I know a lot of people THINK they are “out of memory” due to having strings in SRAM instead of PROGMEM, but who ever runs out of FLASH?

Not to mention the tricky syntax that dtostrf uses.

preferences.jpg

Is a selectable preference REALLY so hard to use?

Krupski:
Honestly... when has someone ever struggled with program size? I know a lot of people THINK they are "out of memory" due to having strings in SRAM instead of PROGMEM, but who ever runs out of FLASH?

I am very nearly out of FLASH on one of my products running on a ProMini, and there is very little left that can be optimized further. There is just a LOT of functionality in there. It's not at all rare, or even unusual.

Regards,
Ray L.

pYro_65:
If you have 1.6.5 and above, you can download my PrintEx library from the library manager.
Or it can be manually installed in versions 1.5.x and up.

Among other things, it includes printf with floating point support.

#include <PrintEx.h>

PrintEx serial = Serial;

void setup(){
 Serial.begin(9600);
 serial.printf("/%31n\\n| A floating point number: %f |\n\%31n/", ‘=’, 3.14f, ‘=’ );
}

void loop() {}




Produces:


/===============================
| A floating point number: 3.14 |
===============================/




The readme has an overview of its usage.
[https://github.com/Chris--A/PrintEx](https://github.com/Chris--A/PrintEx#printex-library-for-arduino-)
    fprintf (stdout, "I like cherry PI! %.2f\n", M_PI);

…produces

[b]I like cherry PI! 3.14[/b]

…no special library required. :slight_smile:

RayLivingston:
I am very nearly out of FLASH on one of my products running on a ProMini, and there is very little left that can be optimized further. There is just a LOT of functionality in there. It’s not at all rare, or even unusual.

Regards,
Ray L.

I’m not saying it NEVER happens (running out of flash). I’m just sure it’s quite rare… and that’s the point of having the FP library linkage as an OPTION… to turn it off if you need the last byte of space.

Krupski:
Is a selectable preference REALLY so hard to use?

No, if people know they exist. I have done a lot of support for software where people ask how do you do so-and-so and you say "check the box in the configuration dialog" and they reply "what? there's a configuration dialog"?

That's what you have to deal with.

Krupski:

    fprintf (stdout, "I like cherry PI! %.2f\n", M_PI);

...produces

[b]I like cherry PI! 3.14[/b]

...no special library required. :slight_smile:

No, but a special part of the Arduino program is required, then it needs recompiling....

Really???

There is something I don't understand, perhaps someone can shed some light on this.

Here is the function for dtostrf()

char *dtostrf (double val, signed char width, unsigned char prec, char *sout) {
char fmt[20];
sprintf(fmt, "%%%d.%df", width, prec);
sprintf(sout, fmt, val);
return sout;
}

It uses sprintf twice and it also uses f in the format.

Why is this better to use than my function that doesn't use sprintf at all and yet still converts the float value to a string?

dtostrf uses 2028 bytes
mine uses 1894 bytes.

134 byte difference
I can't test speed as I don't have a board to upload it to.

That version is for ARM-based targets that have plenty of Flash for a full printf version.
The original AVR version does not use printf.

Now allows negative floating values.

/*
  Had to make my own pow() function as the standard pow() has a return type of double,
  and for some reason type casting from a double to an int produces 1 less that of the value.
  Ex. int(pow(10, 3)) returns 999 instead of 1000. (using IDE 1.6.0 on an UNO)
*/

void setup()
{
  // put your setup code here, to run once:
  Serial.begin(115200);

  char myB[10]; // buffer
  //ftos(myB, 4, 3, 1234.567); // 450 bytes IDE 1.6.0 using Uno R3
  dtostrf(1234.567, 4, 3, myB); // 2,028 bytes IDE 1.6.0 using Uno R3
  Serial.println(myB);
}

void loop() {
  // put your main code here, to run repeatedly:
}

char * ftos(char * buf, byte W, byte D, float data)
{
  byte shf = 0;
  if (data < 0)
  {
    data *= -1;
    shf = 1;
    buf[0] = '-';
  }
  long Wdata = data, tmp;
  float dec = data - long(data);

  // get the first whole number and convert it
  buf[0 + shf] = Wdata / Pow(10, W - 1) + '0';
  tmp = Wdata % Pow(10, W - 1);

  //now get the rest of the whole numbers
  for (byte i = 1; i < W; i++)
  {
    long factor = Pow(10, W - 1 - i);
    buf[i + shf] = (tmp / factor) + '0';
    tmp %= factor;
  }

  buf[W + shf] = '.'; // add the decimal point

  // now do the decimal numbers
  for (byte i = 0; i < D; i++)
  {
    dec *= 10;
    buf[W + i + 1 + shf] = (long(dec) % 10) + '0';
  }
  // don't forget the NULL terminator
  buf[W + D + 1 + shf] = NULL;
  return buf;
}

unsigned long Pow(long V, byte shift)
{
  unsigned long Val = 1;
  while (shift-- != 0)
    Val *= V;

  return Val;
}

Code:

/*
  Had to make my own pow() function as the standard pow() has a return type of double,
  and for some reason type casting from a double to an int produces 1 less that of the value.
  Ex. int(pow(10, 3)) returns 999 instead of 1000. (using IDE 1.6.0 on an UNO)
*/

void setup()
{
  // put your setup code here, to run once:
  Serial.begin(115200);

  char myB[10]; // buffer
  char myB2[10];
  
  unsigned long current = micros();
  ftos(-1234.567, 4, 3, myB);
  Serial.println(micros() - current);
  
  delay(1);
  
  current = micros();
  dtostrf(-1234.567, 4, 3, myB2);
  Serial.println(micros() - current);
  
  Serial.println(myB);
  Serial.println(myB2);
}

void loop() {
  // put your main code here, to run repeatedly:
}

char * ftos(float data, byte W, byte D, char * buf)
{
  byte shf = 0;
  if (data < 0)
  {
    data *= -1;
    shf = 1;
    buf[0] = '-';
  }
  long Wdata = data, tmp;
  float dec = data - long(data);

  // get the first whole number and convert it
  buf[0 + shf] = Wdata / Pow(10, W - 1) + '0';
  tmp = Wdata % Pow(10, W - 1);

  //now get the rest of the whole numbers
  for (byte i = 1; i < W; i++)
  {
    long factor = Pow(10, W - 1 - i);
    buf[i + shf] = (tmp / factor) + '0';
    tmp %= factor;
  }

  buf[W + shf] = '.'; // add the decimal point

  // now do the decimal numbers
  for (byte i = 0; i < D; i++)
  {
    dec *= 10;
    buf[W + i + 1 + shf] = (long(dec) % 10) + '0';
  }
  // don't forget the NULL terminator
  buf[W + D + 1 + shf] = NULL;
  return buf;
}

unsigned long Pow(long V, byte shift)
{
  unsigned long Val = 1;
  while (shift-- != 0)
    Val *= V;

  return Val;
}

Speed results:
Positive number: 1234.567
ftos: 132 microseconds @ 2,388 bytes
dtostrf: 108 microseconds @ 3,680 bytes
diff: 1,292 bytes 22.2% slower

Negative Number: -1234.567
ftos: 424 microseconds @ 3,644 bytes
dtostrf: 108 microseconds @ 3680 bytes
diff: 36 bytes 292.6% slower (damn)

Conclusion: mine lost on speed, but it won in lowest memory usage.

I know this topic is old but just to help other on this topic:

I made a small and simple printf like function for interpolation.

ctkjose:
I know this topic is old but just to help other on this topic:

I made a small and simple printf like function for interpolation.

A simple printf like interpolation for Arduino · GitHub

Check out my Arduino library for providing printf:

All you do is either make a new directory in "libraries" named "Stdinout" and copy the .cpp and .h files into it, or "install" the library using the IDE.

Now, say you want to use printf with the serial port. After opening the serial port as usual, simply add the line

Stdio.open (Serial);

to your code. Of course, you first need to #include "Stdinout" in your sketch!

Note that this only provides stdin/out/err support - it does not give you floating point.