Почему спецификатор для `float` не определен в`printf`?
Похоже, что могло бы быть, есть (по крайней мере, в C99) модификаторы длины, которые могут быть применены к int
: %hhd
, %hd
, %ld
а также %lld
имею в виду signed char
, short
, long
а также long long
, Существует даже модификатор длины, применимый к double
: %Lf
средства long double
,
Вопрос в том, почему они опустили float
? Следуя шаблону, это могло быть %hf
,
8 ответов
Потому что в вызовах C-функций float
аргумент повышается (то есть преобразуется) в double
, так printf
получает double
и будет использовать va_arg(arglist, double)
чтобы получить его внутри его реализации.
В прошлом (C89 и K&R C) каждый float
аргумент был преобразован в double
, Текущий стандарт пропускает это продвижение для функций фиксированной арности, которые имеют явный прототип. Это связано с (и подробности объяснены в) ABI и соглашениями о вызовах реализации. Практически говоря, float
Значение часто загружается в регистр с плавающей запятой при передаче в качестве аргумента, но детали могут отличаться. Прочитайте в качестве примера спецификацию ABI для Linux x86-64.
Кроме того, нет практической причины давать конкретную строку управления форматом для float
так как вы можете настроить ширину вывода (например, с помощью %8.5f
) как и хотел, и %hd
гораздо полезнее (почти необходимо) в scanf
чем в printf
Кроме того, я думаю, что причина (чтобы опустить %hf
указав float
- повышен до double
в вызывающей printf
) является историческим: сначала C был языком системного программирования, а не HPC (Fortran предпочитался в HPC, возможно, до конца 1990-х годов) и float
было не очень важно; это было (и есть) мысль о том, как short
способ снизить потребление памяти. А современные FPU достаточно быстрые (на настольных или серверных компьютерах), чтобы избежать использования float
кроме как для использования меньшего количества памяти. Вы в основном должны верить, что каждый float
где-то (возможно, внутри FPU или CPU) преобразован в double
,
На самом деле ваш вопрос можно перефразировать так: почему %hd
существует для printf
(где это в принципе бесполезно, так как printf
получает int
когда ты передашь short
; тем не мение scanf
нужно!) Я не знаю почему, но я думаю, что в системном программировании это может быть более полезным.
Вы можете потратить время на лоббирование следующего стандарта ISO C, чтобы получить %hf
принят printf
за float
(повышен до double
в printf
звонки, как short
-S получить повышение в int
), с неопределенным поведением, когда значение двойной точности выходит за пределы float
и симметрично %hf
принят scanf
за float
указатели. Удачи на этом.
Из-за продвижения аргумента по умолчанию.
printf()
переменная функция аргумента (...
в своей подписи) все float
доводы доводятся до double
,
C11 §6.5.2.2 Вызовы функций
6 Если выражение, обозначающее вызываемую функцию, имеет тип, который не включает в себя прототип, целочисленные преобразования выполняются для каждого аргумента и аргументов, которые имеют тип
float
повышены доdouble
, Это называется продвижением аргументов по умолчанию.7 Многоточие в объявлении прототипа функции приводит к тому, что преобразование типа аргумента останавливается после последнего объявленного параметра. Повышение аргументов по умолчанию выполняется на конечных аргументах.
Из-за продвижения аргументов по умолчанию при вызове функций с переменным числом float
значения неявно преобразуются в double
до вызова функции, и нет возможности передать float
значение для printf
, Так как нет возможности передать float
значение для printf
нет необходимости в явном спецификаторе формата для float
ценности.
Сказав это, AntoineL поднял интересный момент в комментарии, что %lf
(в настоящее время используется в scanf
соответствовать типу аргумента double *
) возможно, когда-то стоял за long float
", который был синонимом типа в дни до C89, согласно странице 42 обоснования C99. По этой логике, возможно, имеет смысл %f
должен был стоять за float
значение, которое было преобразовано в double
,
Учитывая hh
а также h
модификаторы длины, %hhu
а также %hu
представить четко определенный вариант использования для этих спецификаторов формата: вы можете напечатать младший значащий байт большого unsigned int
или же unsigned short
без приведения, например:
printf("%hhu\n", UINT_MAX); // This will print (unsigned char) UINT_MAX
printf("%hu\n", UINT_MAX); // This will print (unsigned short) UINT_MAX
Не совсем ясно, что такое сужение конверсии от int
в char
или же short
приведет к, но это, по крайней мере, определяется реализацией, то есть реализация необходима для фактического документирования этого решения.
Следуя шаблону, это должно было быть
%hf
,
Следуя шаблону, который вы наблюдали, %hf
следует преобразовать значения за пределы диапазона float
вернуться к float
, Тем не менее, этот вид сужения преобразования из double
в float
приводит к неопределенному поведению, и нет такой вещи, как unsigned float
, Шаблон, который вы видите, не имеет смысла.
Чтобы быть формально правильным, %lf
не обозначает long double
аргумент, и если вы должны были передать long double
аргумент, который вы будете вызывать неопределенное поведение. Из документации ясно, что:
l
(ell) ... не влияет на следующееa
,A
,e
,E
,f
,F
,g
, или жеG
спецификатор конверсии.
Я удивлен, что никто больше не понял это? %lf
обозначает double
аргумент, так же, как %f
, Если вы хотите напечатать long double
использовать %Lf
(заглавная буква)
.
Впредь должно быть понятно, что %lf
для обоих printf
а также scanf
соответствовать double
а также double *
аргументы... %f
является исключительным только из-за продвижения аргумента по умолчанию, по причинам, упомянутым ранее.
... а также %Ld
не значит long
, или. Это означает неопределенное поведение.
Из стандарта ISO C11, 6.5.2.2 Function calls /6
а также /7
, обсуждая вызовы функций в контексте выражений (мой акцент):
6 / Если выражение, обозначающее вызываемую функцию, имеет тип, который не включает в себя прототип, целочисленные преобразования выполняются для каждого аргумента, а аргументы, имеющие тип float, повышаются до двойного. Это называется продвижением аргументов по умолчанию.
7 / Если выражение, обозначающее вызываемую функцию, имеет тип, который включает в себя прототип, аргументы неявно преобразуются, как если бы посредством присваивания, в типы соответствующих параметров, принимая тип каждого параметра как неквалифицированную версию его объявленный тип. Многоточие в объявлении прототипа функции останавливает преобразование типа аргумента после последнего объявленного параметра. Повышение аргументов по умолчанию выполняется на конечных аргументах.
Это означает, что любой float
аргументы после ...
в прототипе преобразуются в double
и printf
семейство звонков определяется таким образом (7.21.6.11
et seq):
int fprintf(FILE * restrict stream, const char * restrict format, ...);
Так что, так как нет возможности printf()
-семейные вызовы для фактического получения числа с плавающей запятой, не имеет особого смысла иметь специальный спецификатор формата (или модификатор) для него.
Принимая во внимание scanf
имеет отдельные спецификаторы формата для float, double или long double, я не понимаю, почему printf
и аналогичные функции не были реализованы подобным образом, но так в итоге оказались C / C++ и стандарты.
Может быть проблема с минимальным размером для операции push или pop в зависимости от процессора и текущего режима, но это может быть обработано с заполнением по умолчанию, аналогично выравниванию по умолчанию локальных переменных или переменных в структуре. Microsoft отказалась от поддержки 80 бит (10 байт) long double
когда он перешел с 16-битных на 32 / 64-битные компиляторы, теперь лечится long double
так же, как double
с (64 бита / 8 байт). Они могли бы дополнить их до 12 или 16-байтовых границ по мере необходимости, но это не было сделано.
Когда C был изобретен, все значения с плавающей точкой были преобразованы в общий тип (т.е. double
) перед использованием в вычислениях или передачей в функции (включая) printf
так что не было необходимости printf
проводить какие-либо различия между типами с плавающей точкой.
В целях повышения арифметической эффективности и точности стандарт IEEE-754 определил 80-битный тип, который был больше, чем обычный 64-битный. double
но может быть обработано быстрее. Намерение состояло в том, чтобы дать выражение как a=b+c+d;
было бы быстрее и точнее преобразовать все в 80-битный тип, сложить три 80-битных числа и преобразовать результат в 64-битный тип, чем вычислять сумму (b+c)
как 64-битный тип, а затем добавить это к d
,
В интересах поддержки нового типа ANSI C определил новый тип long double
какие реализации могут иметь ссылку на новый 80-битный тип или 64-битный double
, К сожалению, несмотря на то, что цель 80-битного типа IEEE-754 состояла в том, чтобы все значения автоматически переводились в новый тип так, как они были представлены. double
, ANSI сделал это так, чтобы новый тип был передан printf
или другие вариационные методы, отличные от других типов с плавающей точкой, что делает такое автоматическое продвижение невыгодным.
Следовательно, оба типа с плавающей точкой, которые существовали при создании C, могут использовать один и тот же %f
спецификатор формата, но long double
который был создан позже, требует другого %Lf
спецификатор формата (с заглавной буквы L
).
%hhd
, %hd
, %ld
а также %lld
были добавлены к printf
чтобы сделать строки формата более совместимыми с scanf
даже если они избыточны для printf
из-за продвижения аргумента по умолчанию.
Так почему не было %hf
добавлено для float
? Это легко: смотреть на scanf
поведение, float
уже имеет спецификатор формата. Это %f
, И спецификатор формата для double
является %lf
,
Тот %lf
это именно то, что C99 добавил к printf
, До C99 поведение %lf
был неопределенным (из-за пропуска любого определения в стандарте). Начиная с C99, это синоним %f
,
Читая обоснование ниже fscanf, можно найти следующее:
Новая функция C99: в C99 были добавлены модификаторы длины hh и ll. ll поддерживает новый тип long long int. hh добавляет возможность обрабатывать типы символов так же, как и все другие целочисленные типы; это может быть полезно при реализации макросов, таких как SCNd8 в (см. 7.18).
Так предположительно hh
был добавлен с целью оказания поддержки всем новым stdint.h
типы. Это может объяснить, почему модификатор длины был добавлен для маленьких целых чисел, но не для маленьких чисел с плавающей точкой.
Это не объясняет, почему С90 h
но нет hh
хоть. Язык, указанный в C90, не всегда последовательный, простой. И более поздние версии унаследовали несоответствие.