Печать числа с плавающей запятой в C, избегая повышения вариативного параметра до двойного

Как я могу напечатать (то есть на стандартный вывод) float в C, без повышения его до double при передаче в printf?

Проблема здесь в том, что функции с переменным числом переменных в C преобразуют все параметры float в double, что приводит к двум ненужным преобразованиям. Например, если вы включите -Wdouble-promotion в GCC и скомпилируете

float f = 0.f;
printf("%f", f);

ты получишь

warning: implicit conversion from 'float' to 'double' when passing argument to function

У меня относительно небольшая вычислительная мощность (ARM Cortex-M3 с тактовой частотой 72 МГц), и я определенно испытываю затруднения при выводе данных с плавающей запятой в формате ASCII. Поскольку в архитектуре изначально отсутствует аппаратный FPU, необходимость преобразования между одинарной и двойной точностью не помогает.

Есть ли способ более эффективно печатать поплавок в прямом C?


person Xo Wang    schedule 02.04.2011    source источник
comment
Если кто-нибудь столкнется с этим, я закончил тем, что просто использовал base64 для своих данных и декодировал/форматировал/красиво распечатывал все на хост-компьютере. Я также рассматривал Boost::Karma, но это потребовало бы C++, и был хороший шанс что это не спасет меня от размера кода.   -  person Xo Wang    schedule 28.07.2011


Ответы (4)


Отказ от повышения не спасет вас ни от чего, так как внутренняя double (или, что более вероятно, long double) арифметика printf займет как минимум в 1000 раз больше времени. Точная печать значений с плавающей запятой непроста.

Однако, если вас не волнует точность и вам просто нужно быстро распечатать приблизительные значения, вы можете свернуть свой собственный цикл для выполнения печати. Пока ваши значения не слишком велики, чтобы поместиться в целочисленный тип, сначала преобразуйте и напечатайте недробную часть как целое число, затем вычтите это и умножьте цикл на 10 и снимите целую часть, чтобы напечатать дробную часть по одной цифре за раз (буферизируйте ее в строку для лучшей производительности).

Или вы можете просто сделать что-то вроде:

printf("%d.%.6d", (int)x, (int)((x-(int)x)*1000000));
person R.. GitHub STOP HELPING ICE    schedule 02.04.2011
comment
Я понял, что большинство чисел с плавающей запятой, которые мне нужно было напечатать, находились в диапазоне [0, 1], поэтому я смог округлить их до целых чисел для печати. Это значительно улучшило производительность. - person Xo Wang; 16.04.2011

К сожалению, printf не поддерживает обработку простых чисел с плавающей запятой.

Это означает, что вам придется написать свою собственную функцию печати. Если вам не нужна полная выразительная мощь printf, вы можете легко преобразовать значение с плавающей запятой в целую часть и часть, представляющую число десятичных знаков, и распечатать обе, используя целые числа.

Если, с другой стороны, вы просто хотите избавиться от предупреждения, вы можете явно привести float к double.

person Lindydancer    schedule 02.04.2011

Я думаю, что это не имеет значения - printf уже такая трудоемкая неприятная вещь, что эти преобразования не должны иметь значения. Время преобразования float в double должно быть намного меньше, чем преобразование любого числа в ascii (вы должны/могли бы профилировать свой код, чтобы получить окончательный ответ). Единственным оставшимся решением было бы написать собственную пользовательскую процедуру вывода, которая преобразует float-> ascii, а затем использует puts (или что-то подобное).

person flolo    schedule 02.04.2011
comment
Я не согласен. Самая затратная часть преобразования с плавающей запятой в ascii — это деление на 10. И это происходит быстрее, если работать с числом с плавающей запятой, а не с двойным числом. - person edgar.holleis; 02.04.2011
comment
@edgar: Конечно, есть операции с плавающей запятой, которые стоят вдвое больше, чем с плавающей запятой. Но реализация div на 10 зависит от реализации библиотеки. Вы также можете просто работать с битами мантиссы с целочисленными операциями для получения цифр. Это сильно зависит от используемой libc (которая для встроенных обычно не является полной большой реализацией). Вот почему я сказал, что вам нужен профиль для окончательного ответа. И единственный способ избежать — написать собственную функцию. - person flolo; 02.04.2011

  • Первый подход: используйте ftoa вместо printf. Профиль.

  • Для повышения гибкости вывода я бы зашел в исходный код stdlib вашего компилятора, возможно, в любом случае, производного от gcc, нашел реализацию printf и скопировал соответствующий код для преобразования double -> ascii. Перепишите его в float -> ascii.

  • Затем вручную измените один или два известных сайта вызова на вашу новую (невариативную) версию и профилируйте ее.

  • Если это решит вашу проблему, вы можете подумать о переписывании собственного printf на основе версии из stdlib, в результате чего вместо float вы передаете float*. Это должно избавиться от автоматического продвижения.

person edgar.holleis    schedule 02.04.2011