Как вывести float с нужным числом знаков на Arduino?
Обновлено Редакция GAW
В ядре Arduino для AVR печать числа с плавающей точкой — собственная функция printFloat() из Print.cpp, а sprintf() берётся из avr-libc. Ведут они себя по-разному, отсюда и путаница.
Три способа и что они печатают
| Код | Вывод для x = 3.14159 | Почему |
|---|---|---|
Serial.print(x) | 3.14 | в Print.h второй аргумент по умолчанию — 2 знака |
Serial.print(x, 4) | 3.1416 | округление прибавлением 0,00005 перед печатью |
sprintf(buf, "%.4f", x) | ? | стандартная vfprintf из avr-libc не преобразует числа с плавающей точкой |
dtostrf(x, 0, 4, buf) | 3.1416 | функция avr-libc из stdlib.h, работает без особых ключей сборки |
platform.txt ядра 1.8.8 подключает только -lm и не добавляет printf_flt, поэтому %f в скетче по умолчанию даёт вопросительный знак.
Строка с числом для дисплея или передачи
char num[12];
char line[24];
float t = 23.456;
dtostrf(t, 0, 1, num); // width 0: no padding, 1 digit after the point
snprintf(line, sizeof(line), "T=%s C", num);
Serial.println(line); // T=23.5 C
Ширина поля width в dtostrf() — минимальная, вместе с точкой и знаком; отрицательная выравнивает число влево. Буфер должен вместить все символы и завершающий ноль — длину функция не проверяет.
Почему цифр меньше, чем хочется
На AVR тип double совпадает с float: так в avr-gcc до 9-й версии и по умолчанию в более новых (GCC Wiki). У 32-битного числа около 7 значащих цифр, поэтому Serial.print(1234567.89, 2) напечатает 1234567.87: ближайшее к этому числу значение float — 1 234 567,875 (наш расчёт). Как устроено число с плавающей точкой и где теряются разряды, показывает конвертер систем счисления в режиме IEEE 754, а типы и их размеры на микроконтроллерах разобраны в статье «Язык C для встраиваемых систем». Если печать тормозит программу, причина — в буфере передатчика: об этом ответ «Serial.print замедляет программу».
Похожие вопросы
Почему Serial.print показывает ovf?
Потому что printFloat() печатает целую часть как unsigned long и для чисел больше 4 294 967 040 по модулю сразу выводит «ovf». Экспоненциальной записи у Print в ядре AVR нет: большие числа делят на степень десяти и печатают вместе с порядком отдельно.
Можно ли напечатать 10 знаков после запятой?
Напечатать можно, но смысла нет: в ядре AVR double — тот же 32-битный float, а у него 24 бита мантиссы, около 7 значащих десятичных цифр (2²⁴ ≈ 16,8 млн, наш расчёт). Остальные цифры — шум округления.
Как включить %f в sprintf?
Подключить вариант vfprintf с плавающей точкой ключами компоновщика -Wl,-u,vfprintf -lprintf_flt, как описано в документации avr-libc. В Arduino IDE их добавляют к флагам компоновки в собственном platform.local.txt; прошивка вырастет на код форматирования чисел с плавающей точкой.
Источники
- Arduino. ArduinoCore-avr 1.8.8: cores/arduino/Print.h, Print.cpp — Print.h: print(double, int = 2) и println(double, int = 2) — два знака по умолчанию; Print.cpp, printFloat(): nan и inf печатаются словами, при |x| > 4 294 967 040 — «ovf» (константа подобрана опытным путём); число округляется прибавлением 0,5 · 10^−digits, целая часть печатается как unsigned long, дробная — по одной цифре
- AVR-LibC 2.3.2: include/stdio.h, include/stdlib.h, libc/stdio/vfprintf.c (github.com/avrdudes/avr-libc) — stdio.h, описание vfprintf: три варианта — по умолчанию всё, кроме преобразований с плавающей точкой, минимальный и с плавающей точкой; полный включается ключами компоновщика -Wl,-u,vfprintf -lprintf_flt или, начиная с 2.3, -Wl,--defsym,vfprintf=vfprintf_flt; vfprintf.c: без PRINTF_FLT для e, f, g аргумент пропускается и выводится «?»; stdlib.h: dtostrf(val, width, prec, s) — формат [-]d.ddd, width — минимальная ширина поля (отрицательная — выравнивание влево), prec — знаков после точки
- GCC Wiki. avr-gcc — в AVR GCC до версии 9 double и long double — 32 бита и устроены как float; в v10 и новее раскладка задаётся при сборке компилятора, по умолчанию double — как float
Нашли ошибку?
Смотрите также
- Serial и USART0Serial.begin, U2X0, исключение 57600, буферы и прерывания
- C для микроконтроллеровТипы, stdint, volatile, атомарность, регистры, константы во Flash
- Почему Serial.print замедляет программу Arduino?Буфер 64 байта полон — print ждёт линию; на 9600 бод ~1 мс на байт
- Конвертер HEX, BIN, DECДополнительный код, float, байты и биты
- Почему printf на STM32 ничего не выводит, пока нет перевода строки?stdout в newlib буферизуется построчно: \n, fflush(stdout) или setvbuf _IONBF