Почему printf() не выводит это целое число как число с плавающей запятой?

У меня есть этот код:

#include <stdio.h>

int main()
{
    int i = 12345;
    printf("%f", i);

    return 0;
}

printf() выведет 0.000000, не должен printf() интерпретировать биты, содержащиеся в i как число с плавающей точкой?

8 ответов

Это технически неопределенное поведение, поэтому может случиться что угодно, например, то, что @Art описывает в своем ответе.

Таким образом, этот ответ является объяснением того, что произойдет, если вы попытаетесь напечатать 12345 как число с плавающей точкой, предполагая, что printf действительно видит правильное 32-битное значение.

Давайте проанализируем двоичное представление для числа, которое вы пытаетесь представить.

Используя http://www.h-schmidt.net/FloatConverter/IEEE754.html мы видим, что десятичное число 12345 имеет следующее 32-битное представление:

decimal      12345
hexadecimal  0x00003039

Преобразованный бит в бит в 32-битное значение с плавающей точкой IEEE-754, это представляет:

float        1.7299E-41

давайте попробуем напечатать это:

#include <stdio.h>

int main() {
    printf("%f\n", 1.7299E-41);
    return 0;
}

Это печатает:

0.000000

Теперь давайте прочитаем printf man-страница, найденная здесь: http://linux.die.net/man/3/printf

F, F

Двойной аргумент округляется и преобразуется в десятичную запись в стиле [-]ddd.ddd, где количество цифр после символа десятичной точки равно спецификации точности. Если точность отсутствует, она принимается за 6; если точность явно равна нулю, символ десятичной точки не появляется. Если появляется десятичная точка, перед ней должна быть хотя бы одна цифра.

1.7299E-41 Значение не может быть представлено с использованием этого флага с точностью по умолчанию, поэтому полученный результат действительно является правильным значением.

Технически это неопределенное поведение.

Вы должны использовать союз, чтобы сделать это и распечатать его с %e вместо %fпотому что это небольшое число:

#include <stdio.h>
union int2float
{
  int a;
  float b;
};

int main()
{
    union int2float tmp;
    tmp.a = 12345;

    printf("%e\n", tmp.b);

    return 0;
}

Это приводит к 1.729903e-41,

с %.100f ты получишь 0.0000000000000000000000000000000000000000172990295420898667405534417057140146406548336724655872023410 в качестве вывода.

Наиболее вероятная причина printf не интерпретирует биты в i в качестве числа с плавающей запятой является то, что printf не видит i совсем. Я подозреваю, что вы используете x86_64 или аналогичную платформу, где аргументы передаются функциям в регистрах. Обычно printf интерпретировал бы ваш int как любой спецификатор формата, который вы ему дали. Но аргументы с плавающей точкой для функций обрабатываются по-разному в x86_64 и помещаются в разные регистры. Таким образом, причина, по которой вы получаете 0 как результат printf извлекает первый аргумент с плавающей запятой после строки формата вместо первого регистра общего назначения и печатает его. Поскольку вы не использовали никаких регистров с плавающей запятой до вызова printf они, скорее всего, обнуляются с момента запуска программы.

Вот эксперимент, который вы можете попробовать:

#include <stdio.h>

int
main(int argc, char **argv)
{
        printf("%f\n", 17.42);
        printf("%f\n", 0x87654321);
        return 0;
}

Я получаю этот вывод:

$ ./foo
17.420000
17.420000

Сказав все это, единственный правильный ответ: это неопределенное поведение, не делайте этого. Но интересно посмотреть, что происходит под капотом.

Если вы хотите вникнуть в это и запустить Linux/MacOS/*BSD, этот документ, раздел 3.2.3 описывает, как аргументы передаются функциям (включая функции varargs, такие как printf). Windows делает вещи немного по-другому, но в этом случае результат на Windows будет точно таким же. printf в Linux/MacOS/*BSD ожидает, что аргумент будет напечатан в %xmm0 (%xmm1 в Windows), пока ваш вызов передает его в %rsi (%rdx на винде).

Конечно, если я ошибаюсь в x86_64, не обращайте внимания на все, что я сказал, и посмотрите на ответ @SirDarius, потому что это относится ко многим (особенно старым) архитектурам. Если вы используете альфа, sparc, powerpc, arm и т. Д., То вы сами по себе. Вызов printf один раз с int, один раз с плавающей точкой, разберите скомпилированный код и посмотрите, как передаются аргументы.

Если вы хотите напечатать int как float, приведите его:

printf("%f", (float)i);

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

printf("%f", (float) i);

Когда вы используете %f спецификатор формата, вы должны использовать переменную float и распечатать значение. но вы объявили i как целое число и пытается печатать, используя %f это напечатает некоторое значение. Поскольку это целое число, вы должны использовать %d спецификатор. В printf %f будет искать переменную с плавающей точкой. но не будет переменной с плавающей точкой, поэтому она печатает некоторое значение.

Вы можете набрать целое значение во время печати.printf("%f",(float)i);

Делать i как float Тип, вам нужно сделать приведение типа.

Ты можешь использовать

printf("%f", (float)i);

Пример.

float myFloat;
int myInt; 
myFloat = (float)myInt ;        // Type casting

Попробуй это

int i = 12345;
printf("%.2f", i/1.0f);
Другие вопросы по тегам