Arduino sprintf, and turning a float into text
Beginner
Or see every lesson.
- 455parts on the bench
- 43boards running now
- 1.00xreal time, on every board
sprintf(buffer, format, values...) writes formatted text into a char array: sprintf(buf, "%02d:%02d", 7, 5) makes "07:05". %d is a whole number, %s is text, %x is hex, and %02d pads to two digits with zeros. On an Uno there is one big catch: %f does not work, and a float prints as a question mark. Turn the float into text first with dtostrf() or String(value, 2).
The circuit above is a DHT11 on an Uno. Press Run and it prints "temperature 22 C, humidity 45 %" every two seconds, built from five Serial.print() calls. Below, the same kind of line comes from one snprintf, along with a clock with leading zeros, a float to two decimals, long numbers, hex, and the ways a format goes wrong, each run on an Uno with what it printed.
A clock with leading zeros
The format is text with gaps in it. Each % starts a gap and the values after the format fill them in order. %02d means a whole number, at least two characters wide, padded with zeros, so 7 hours and 5 minutes printed "07:05".
The buffer has to hold every character plus the zero byte that ends the text: "07:05" is five characters, so char buf[6]. sprintf has no idea how big your buffer is.
void setup() {
Serial.begin(9600);
char buf[6];
int hours = 7;
int minutes = 5;
sprintf(buf, "%02d:%02d", hours, minutes);
Serial.println(buf);
}
void loop() {
}Why %f prints a question mark
The Uno’s sprintf leaves floats out to save flash, so %f and %.2f both print "?". The first line below printed "t=?". It is not a bug in your sketch, and the Arduino IDE does the same on an Uno, Nano or Mega.
dtostrf(value, width, decimals, buffer) turns a float into text that %s can take. With a width of 1 it is as short as it can be: the second line printed "t=23.46". A wider width pads on the left, so dtostrf(23.456, 6, 2, s) gave " 23.46". String(celsius, 2) does the same in one step and printed 23.46.
ESP32 boards have the full sprintf: the same %.2f printed "t=23.46" on an ESP32-C3, and their Serial has printf() too. The Uno’s Serial in the Arduino IDE has no printf(), so on an Uno build the text with sprintf and print that.
void setup() {
Serial.begin(9600);
char buf[16];
float celsius = 23.456;
sprintf(buf, "t=%.2f", celsius);
Serial.println(buf);
char number[8];
dtostrf(celsius, 1, 2, number);
sprintf(buf, "t=%s", number);
Serial.println(buf);
String text = String(celsius, 2);
Serial.println(text);
}
void loop() {
}The sensor line in one call
Here are the DHT11’s readings as one line. dtostrf gives the temperature one decimal, the humidity is cast to an int for %d, and %% prints a percent sign. With 22.4 and 45 it printed "22.4 C, 45 %".
snprintf is sprintf with the buffer’s size passed in, so it stops before the end and never writes past it. Use it whenever the text can vary in length.
void setup() {
Serial.begin(9600);
float celsius = 22.4;
float humidity = 45.0;
char temp[8];
dtostrf(celsius, 4, 1, temp);
char line[32];
snprintf(line, sizeof(line), "%s C, %d %%", temp, (int)humidity);
Serial.println(line);
}
void loop() {
}Long numbers need %ld
%d is an int, 16 bits on an Uno. Give it a long holding 100000 and it printed -31072, the bottom 16 bits read as an int. Clang warned format specifies type 'int' but the argument has type 'long'; GCC words it format '%d' expects argument of type 'int', but argument 3 has type 'long int'. Use %ld for a long and %lu for an unsigned long such as millis(); %lu printed 4000000.
The same goes for big ints: 40000 does not fit an int, so it is a long and needs %ld as well. Through %d it printed -25536.
Hex, widths and lining things up
%x prints hex in small letters and %X in capitals, so 255 gave ff and FF. 0x%02X on the byte 0x0A printed "0x0A", the usual way to show an I2C address or a register. A width without a zero pads with spaces: %5d put 42 at the right of five characters and %-5d at the left, which lines columns up on the serial monitor.
Three ways it goes wrong
A buffer too small: the sketch below writes "Hello, world", thirteen bytes with its zero, into char small[6]. Nothing warned. It printed "Hello, world" and "next line", and then the sketch started again from the top, over and over, because the extra bytes had overwritten the place setup() returns to. snprintf with a size of 8 cut "abcdefghijk" to "abcdefg", seven letters and the zero byte, and returned 11, the length it wanted, so you can tell the text was cut.
A String in %s: sprintf(buf, "%s", String("hi")) stopped the build with cannot pass non-trivial object of type 'String' to variadic function, and clang added did you mean to call the c_str() method? String("hi").c_str() printed hi.
A value for every gap: sprintf reads one value for each %, in order, whatever you passed. A missing value or one of the wrong type prints nonsense rather than stopping the build, so read the warnings in the build tab.
void setup() {
Serial.begin(9600);
char small[6];
sprintf(small, "%s", "Hello, world");
Serial.println(small);
Serial.println("next line");
}
void loop() {
}Questions
Why does sprintf %f not work on Arduino?
On the Uno, Nano and Mega, sprintf is built without float support to save flash, so %f prints a question mark. Convert the float with dtostrf() or String(value, 2) and print it with %s. ESP32 boards support %f.
How do I convert a float to a string with 2 decimals on Arduino?
String(value, 2) for a String, or dtostrf(value, 1, 2, buffer) for a char array with room for the digits, the point and the zero byte.
What is the difference between sprintf and snprintf?
snprintf also takes the buffer’s size and never writes past it, cutting the text short instead. sprintf writes as much as the format makes, even past the end of the buffer.
How do I print a number with leading zeros on Arduino?
Format it with %02d for two digits or %04d for four, then print the buffer. 7 with %02d becomes 07.
Can I use Serial.printf on an Arduino Uno?
Not in the Arduino IDE: the AVR boards’ Serial has no printf(). Build the line with snprintf and Serial.println() it. ESP32 and ESP8266 boards do have Serial.printf().
Keep going
Where the facts on this page come from
Build this for real
Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.