Как работает эта программа? - PullRequest
88 голосов
/ 04 марта 2010
#include <stdio.h>

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

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


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

Ответы [ 13 ]

238 голосов
/ 04 марта 2010

Это потому, что %d ожидает int, но вы указали число с плавающей точкой.

Используйте %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. Они называются продвижениями аргументов по умолчанию .

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

45 голосов
/ 04 марта 2010

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

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

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

Чтобы проверить байты 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
20 голосов
/ 04 марта 2010

Спецификатор %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.

7 голосов
/ 04 марта 2010

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

7 голосов
/ 04 марта 2010

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

5 голосов
/ 04 марта 2010

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

3 голосов
/ 04 марта 2010

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

#include <stdio.h>

int main() {
    float a = 1234.5f;
    printf("%d\n", (int)a);
    return 0;
}
2 голосов
/ 04 марта 2010

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

#include <iostream.h>

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

Выход:

1234.5 1234
1 голос
/ 04 марта 2010

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

1 голос
/ 04 марта 2010

%d является десятичным

%f является плавающим

см. Больше этих здесь .

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

...