Scanf() - Что такое спецификатор формата/преобразования %a?

В C есть возможность реализовать %a как спецификатор формата внутри строки формата scanf() для форматирования значений с плавающей запятой.

Нравиться:

float v;
scanf("%a",&v);

В стандарте C (моё отношение, в частности, ISO/IEC 9899/2011 (C11)) только меньше объясняется об этом конкретном спецификаторе и ничего не говорит о его отличии от связанных спецификаторов преобразования с плавающей запятой %f, %e и %g :

Ссылка, ISO/IEC 9899/2011, §7.21.6.2:

a,e,f,g — Соответствует необязательному знаковому числу с плавающей запятой, бесконечности или NaN, формат которого совпадает с ожидаемым для последовательности субъекта функции strtod. Соответствующий аргумент должен быть указателем на плавание.

Что делает этот спецификатор и для чего он предназначен? В чем конкретное отличие от других спецификаторов преобразования с плавающей запятой?


person RobertS supports Monica Cellio    schedule 20.12.2019    source источник
comment
Это спецификатор, введенный в C99 и (возможно?) допускающий ввод с плавающей запятой в шестнадцатеричном формате: cppreference.   -  person Adrian Mole    schedule 20.12.2019
comment
@AdrianMole Это ("%a") позволяет вводить числа с плавающей запятой в шестнадцатеричном формате: немного не так. Ввод с плавающей запятой в шестнадцатеричном формате разрешен C99, но работает для %a, %A, %e, %E, %f, %F, %g, %G, %la, %lA, %le, %lE, ... %LG   -  person chux - Reinstate Monica    schedule 20.12.2019
comment
@chux Вот почему я добавил (возможно?) - связанная страница cppreference говорит, что C99 разрешает шестнадцатеричный ввод для чисел с плавающей запятой, а также имеет формат% a, помеченный как специфичный для C99, поэтому я не был уверен.   -  person Adrian Mole    schedule 20.12.2019


Ответы (1)


Все спецификаторы формата %a, %e, %f и %g в scanf выполняют то же преобразование, что указано в цитируемом отрывке из стандарта.

На справочной странице Linux для scanf об этом говорится более подробно:

f Соответствует необязательному числу с плавающей запятой со знаком; следующий указатель должен быть указателем с плавающей запятой.

е Эквивалент f.

г Эквивалент f.

E Эквивалент f.

a (C99) Эквивалент f.

Предположительно, они присутствуют, потому что они также являются спецификаторами формата printf, которые принимают float, но, в отличие от scanf, отличаются выводом, который они производят.

Чтобы проиллюстрировать это, следующий код:

#include <stdio.h>

int main()
{
    char *str[] = { "234.56", "2.3456e2", "2.3456E2", "0x1.d51eb8p+7" };
    unsigned i;

    for (i=0; i<sizeof(str)/sizeof(*str); i++) {
        float f;

        printf("scanning %s\n", str[i]);
        sscanf(str[i], "%f", &f);
        printf("scanned with f: (f)%f, (e)%e, (g)%g, (a)%a\n", f, f, f, f);
        sscanf(str[i], "%g", &f);
        printf("scanned with g: (f)%f, (e)%e, (g)%g, (a)%a\n", f, f, f, f);
        sscanf(str[i], "%e", &f);
        printf("scanned with e: (f)%f, (e)%e, (g)%g, (a)%a\n", f, f, f, f);
        sscanf(str[i], "%a", &f);
        printf("scanned with a: (f)%f, (e)%e, (g)%g, (a)%a\n", f, f, f, f);
    }
    return 0;
}

Выходы:

scanning 234.56
scanned with f: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with g: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with e: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with a: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanning 2.3456e2
scanned with f: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with g: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with e: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with a: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanning 2.3456E2
scanned with f: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with g: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with e: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with a: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanning 0x1.d51eb8p+7
scanned with f: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with g: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with e: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with a: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
person dbush    schedule 20.12.2019
comment
Но разве это не так? %e ведет себя иначе, чем %f, поэтому другие, например %e, не совсем такие же, как %g. Ни один из них не является прямым эквивалентом %f. Это утверждение также означает, что %e эквивалентно %E, что не совсем так. Консенсус этой цитаты таков: все эквивалентны, что довольно смешно. Я не могу поверить, что это написано на справочных страницах Linux. - person RobertS supports Monica Cellio; 20.12.2019
comment
@RobertS-ReinstateMonica — Нет: все сканеры с плавающей запятой семейства scanf() принимают любое допустимое представление с плавающей запятой, включая шестнадцатеричное представление, которое %a и %A создают с семейством printf(). Указание %lf не помешает вам ввести 6.0221409e+23 и получить приближение к числу Авогадро. - person Jonathan Leffler; 20.12.2019
comment
@JonathanLeffler Да, я знаю, что они могут принимать все заданные представления значений с плавающей запятой, но их поведение, то есть то, как они точно преобразуют данное значение, немного отличается. В случае, если бы я согласился с вами и сказал, что все они будут вести себя одинаково, мой вопрос можно было бы также сформулировать так: почему в стандарте предусмотрено так много спецификаторов, если все они выполняют свою работу одинаково? И под эквивалентом я подразумеваю 100% одинаковое поведение. - person RobertS supports Monica Cellio; 20.12.2019
comment
@RobertS-ReinstateMonica См. мое редактирование для примера идентичного поведения. Что касается того, почему они включены, скорее всего, из-за симметрии с printf. - person dbush; 20.12.2019
comment
@RobertS-ReinstateMonica — функции scanf() принимают несколько спецификаторов для чисел с плавающей запятой для симметрии между форматами printf() и scanf(). Долгое время (снова до C99) scanf() имел %lf для чтения double, но printf() использовал только %f (поскольку значения, передаваемые printf(), всегда преобразовывались из float в double по правилам продвижения аргументов по умолчанию). В C99 они добавили %lf в качестве синонима %f, опять же, чтобы улучшить симметрию между двумя семействами функций. - person Jonathan Leffler; 20.12.2019
comment
@JonathanLeffler Итак, концепция, как вы говорите, заключается в том, чтобы сохранить удобочитаемость (или улучшить ее) в коде, поскольку семейство printf() использует одни и те же спецификаторы. Нет особой причины для их использования, так как все они будут рассматриваться как %f, за исключением целей удобочитаемости? Но, и об этом можно было бы спросить в другом вопросе, преобразования ведут себя по-разному при использовании в printf(), несмотря на то, что, конечно, printf() имеет другую цель работы? - person RobertS supports Monica Cellio; 20.12.2019
comment
@RobertS-ReinstateMonica — я не уверен, было ли намерение позволить вам использовать printf("%lf %d %d %lf", dbl1, int1, int2, dbl2); для записи данных, которые могли бы быть прочитаны scanf("%lf %d %d %lf", &dbl1, &int1, &int2, &dbl2);, но если вы используете такие неквалифицированные спецификации преобразования (и не включаете новую строку в конце строка формата printf()), тогда вы можете работать с кодом. Я тщательно избегал %s; он успешно печатает данные с пробелами, но scanf() останавливается на первом пробеле. Как только вы добавите к формату печати практически любой квалификатор, он перестанет использоваться для ввода. - person Jonathan Leffler; 20.12.2019
comment
@RobertS-ReinstateMonica - и да, выходные данные %e, %f, %g и %a в целом разные, хотя %g и %e могут быть похожими для чисел с достаточно большими и достаточно маленькими величинами. - person Jonathan Leffler; 20.12.2019