Как работает эта программа?

88
#include <stdio.h>

int main() {
    float a = 1234.5f;
    printf("%d\n", a);
    return 0;
}

Он отображает 0!! Как такое возможно? В чем причина?


Я намеренно поставил %dв printfутверждение, чтобы изучить поведение printf.

Лазер
источник

Ответы:

239

Это потому, что %dожидает, intно вы предоставили float.

Используйте %e/ %f/ %gдля печати поплавка.


Почему печатается 0: число с плавающей запятой преобразуется в doubleперед отправкой в printf. Число 1234,5 в двойном представлении с прямым порядком байтов:

00 00 00 00  00 4A 93 40

A %dиспользует 32-битное целое число, поэтому печатается ноль. (В качестве теста вы могли printf("%d, %d\n", 1234.5f);бы получить на выходе 0, 1083394560.)


Что касается того, почему floatпреобразован в double, как прототип printf int printf(const char*, ...), из 6.5.2.2/7,

Обозначение с многоточием в деклараторе прототипа функции вызывает остановку преобразования типа аргумента после последнего объявленного параметра. По умолчанию продвижение аргументов выполняется для конечных аргументов.

и с 6.5.2.2/6,

Если выражение, обозначающее вызываемую функцию, имеет тип, который не включает прототип, целочисленные повышения выполняются для каждого аргумента, а аргументы, имеющие тип float, повышаются до double. Они называются продвижением аргументов умолчанию .

(Спасибо Алоку за то, что узнал об этом.)

Kennytm
источник
4
+1 Лучший ответ. Он отвечает как на «стандартное технически правильное» почему, так и на «вашу вероятную реализацию» почему.
Крис Лутц,
12
Я считаю, что вы единственный из 12 человек, который действительно дал ответ, который он искал.
Гейб,
11
Поскольку printfэто функция с переменным числом аргументов, а в стандарте сказано, что для функций с переменным числом аргументов a перед передачей floatпреобразуется в double.
Алок Сингхал,
8
Из стандарта C: «Обозначение с многоточием в деклараторе прототипа функции вызывает остановку преобразования типа аргумента после последнего объявленного параметра. Повышение уровня аргумента по умолчанию выполняется для конечных аргументов». и «... и аргументы, имеющие тип float, повышаются до двойного. Это называется продвижением аргументов по умолчанию ».
Алок Сингхал,
2
Использование неправильного спецификатора формата в printf()вызывает неопределенное поведение .
Prasoon Saurav
45

Технически не говоря нет , каждая библиотека реализует свою собственную, и , следовательно, ваш метод пытается исследования «s поведение, делая то , что вы делаете, не будет много пользы. Вы могли бы попытаться изучить поведение в своей системе, и если да, вам следует прочитать документацию и посмотреть исходный код, чтобы узнать, доступен ли он для вашей библиотеки. printfprintfprintfprintf

Например, на моем Macbook я получаю результат 1606416304с вашей программой.

Сказав это, когда вы передаете floatпеременную функцию, floatона передается как double. Итак, ваша программа эквивалентна объявлению в aвиде файла double.

Чтобы проверить байты a double, вы можете увидеть этот ответ на недавний вопрос здесь, на SO.

Давайте сделаем это:

#include <stdio.h>

int main(void)
{
    double a = 1234.5f;
    unsigned char *p = (unsigned char *)&a;
    size_t i;

    printf("size of double: %zu, int: %zu\n", sizeof(double), sizeof(int));
    for (i=0; i < sizeof a; ++i)
        printf("%02x ", p[i]);
    putchar('\n');
    return 0;
}

Когда я запускаю указанную выше программу, я получаю:

size of double: 8, int: 4
00 00 00 00 00 4a 93 40 

Итак, первые четыре байта из числа doubleоказались равными 0, что может быть причиной того, что вы получили 0результат вашего printfвызова.

Для более интересных результатов мы можем немного изменить программу:

#include <stdio.h>

int main(void)
{
    double a = 1234.5f;
    int b = 42;

    printf("%d %d\n", a, b);
    return 0;
}

Когда я запускаю указанную выше программу на своем Macbook, я получаю:

42 1606416384

С той же программой на машине Linux я получаю:

0 1083394560
Алок Сингхал
источник
Почему вы запрограммировали печать в обратном направлении? Я что-то упускаю? Если b - это int = 42, а '% d' - это целочисленный формат, почему это не второе напечатанное значение, поскольку это вторая переменная в аргументах printf? Это опечатка?
Katastic Voyage
Вероятно, потому что intаргументы передаются в разных регистрах, чем doubleаргументы. printfwith %dпринимает intаргумент, равный 42, а второй, %dвероятно, печатает мусор, поскольку второго intаргумента не было.
Алок
20

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

Двоичное представление 1234.5 похоже на

1.00110100101 * 2^10 (exponent is decimal ...)

С компилятором C, который floatфактически представляет собой двойные значения IEEE754, байты будут (если я не ошибся)

01000000 10010011 01001010 00000000 00000000 00000000 00000000 00000000

В системе Intel (x86) с малым порядком байтов (т. Е. Первым идет младший байт), эта последовательность байтов меняется на обратную, так что первые четыре байта равны нулю. То есть то, что printfраспечатывает ...

См. Эту статью в Википедии о представлении с плавающей запятой в соответствии с IEEE754.

MartinStettner
источник
7

Это из-за представления числа с плавающей запятой в двоичном формате. При преобразовании в целое число остается 0.

Шайхи
источник
1
Кажется, ты единственный, кто понял, о чем он просил. Если, конечно, я не ошибаюсь.
Mizipzor 04
Я согласен с тем, что ответ неточный и неполный, но не ошибочный.
Shaihi 04
7

Потому что вы вызвали неопределенное поведение: вы нарушили контракт метода printf (), солгав ему о типах его параметров, поэтому компилятор может делать все, что ему заблагорассудится. Это могло привести к выводу программы "dksjalk - тупица !!!" и технически это все равно было бы правильно.

Килиан Фот
источник
5

Причина в том, что printf()это довольно глупая функция. Он вообще не проверяет типы. Если вы говорите, что первый аргумент - это int(и это то, что вы говорите %d), он верит вам и берет только байты, необходимые для int. В этом случае, если ваша машина использует четырехбайтовые intи восьмибайтовые байты double( floatпреобразуется во doubleвнутреннюю printf()), первые четыре байта aбудут просто нулями, и это будет напечатано.

Горпик
источник
3

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

#include <stdio.h>

int main() {
    float a = 1234.5f;
    printf("%d\n", (int)a);
    return 0;
}
сганеш
источник
2

Поскольку вы также пометили его с помощью C ++, этот код выполняет преобразование, как вы, вероятно, ожидаете:

#include <iostream.h>

int main() {
    float a = 1234.5f;
    std::cout << a << " " << (int)a << "\n";
    return 0;
}

Выход:

1234.5 1234
Мизипзор
источник
1

%d десятичный

%f плавает

см. больше здесь .

Вы получаете 0, потому что числа с плавающей запятой и целые числа представлены по-разному.

Филип Экберг
источник
1

Вам просто нужно использовать соответствующий описатель формата (% d,% f,% s и т. Д.) С соответствующим типом данных (int, float, string и т. Д.).

Мухаммад Максудур Рехман
источник
Вопрос не в том, как это исправить, а в том, почему это работает так, как работает.
ya23 04
0

эй, он должен был что-то напечатать, поэтому он напечатал 0. Помните, что в C 0 все остальное!

коренастый
источник
1
Как это так? В C нет ничего лучше всего остального.
Влад
если x это что-то, то! x == 0 :)
chunkyguy 05
if (iGot == "все") напечатать "все"; иначе напечатайте «ничего»;
Ник
0

Это не целое число. Попробуйте использовать %f.

танцовщицы
источник
Я предполагаю, что вопрос в том, почему он просто не конвертирует float в int и не отображает «1234»?
Björn Pollex
не ожидайте, что c не позволит вам сделать что-то, что не имеет логического смысла. Да, многие языки дадут 1234, как вы можете ожидать, и, возможно, даже некоторые реализации c не будут думать, что это поведение определено. C позволяет вам повеситься, как родитель, который позволяет вам попробовать крэк.
повторить
Потому что C разработан, чтобы быть гибким.
тангры