Целочисленное значение плавающего типа в C

#include<stdio.h>
 int main()
 {
   float a;
   printf("Enter a number:");
   scanf("%f",&a);
   printf("%d",a);
   return 0;
 }

Я запускаю программу с gcc в Ubuntu. Для значений -

          3.3 it gives value 1610612736 
          3.4 it gives value 1073741824
          3.5 it gives value 0
          3.6 it gives value -1073741824
          4 it gives value 0
          5 it gives value 0

Что происходит? Почему эти значения печатаются? Я делаю это намеренно, но хочу понять, почему это происходит. Детали оценены!

Ответ 1

Функция printf не знает тип передаваемого вами формата, поскольку эта часть является переменной.

int printf(const char* format, ...);
//                             ^^^

В стандарте C передача float будет автоматически повышена до double (C11§6.5.2.2/6), и ничего не будет сделано в стороне вызывающего абонента.

Внутри printf, так как он не знает тип этого ... thingie (§6.7.6.3/9), он должен использовать подсказку из другого места - строку формата. Поскольку вы прошли "%d", он сообщает функции, что ожидается int.

В соответствии со стандартом C это приводит к поведению undefined (§7.21.6.1/8-9), что включает в себя возможность печатать какой-то странный номер, конец истории.

Но что на самом деле происходит? На большинстве платформ double представлен как IEEE 754 binary64float в формате binary32. Числа, которые вы ввели, преобразуются в float, который имеет только 23 бит значения, что означает, что числа будут приближены следующим образом:

3.3 ~ (0b1.10100110011001100110011) × 2¹  (actually: 3.2999999523162842...)
3.4 ~ (0b1.10110011001100110011010) × 2¹  (actually: 3.4000000953674316...)
3.5 = (0b1.11                     ) × 2¹  (actually: 3.5)
3.6 ~ (0b1.11001100110011001100110) × 2¹  (actually: 3.5999999046325684...)
4   = (0b1                        ) × 2²  (actually: 4)
5   = (0b1.01                     ) × 2²  (actually: 5)

Теперь мы преобразуем это в double, который имеет 53 бит значения, которые мы должны вставить 30 двоичных "0" в конце этих чисел, чтобы создать, например,

3.299999952316284 = 0b1.10100110011001100110011000000000000000000000000000000 ×2¹

Это главным образом для получения фактического представления этих чисел, которые:

3.3 → 400A6666 60000000
3.4 → 400B3333 40000000
3.5 → 400C0000 00000000
3.6 → 400CCCCC C0000000
4   → 40100000 00000000
5   → 40140000 00000000

Я рекомендую использовать http://www.binaryconvert.com/convert_double.html, чтобы увидеть, как это происходит в формате ± m × 2 e.

В любом случае, я полагаю, что ваша система представляет собой x86/x86_64/ARM в обычной настройке, что означает, что числа выложены в памяти с помощью малоформатный формат, поэтому переданные аргументы будут похожи на

 byte
  #0   #1   ...          #4   ...            #8 ....
+----+----+----+----+  +----+----+----+----+----+----+----+----+
| 08 | 10 | 02 | 00 |  | 00 | 00 | 00 | 60 | 66 | 66 | 0A | 40 | ....
+----+----+----+----+  +----+----+----+----+----+----+----+----+
 address of "%d"         content of 3.299999952316284
 (just an example)

Внутри printf он потребляет строку формата "%d", анализирует ее, а затем обнаруживает, что требуется int из-за% d, поэтому из байпасного ввода берутся 4 байта, а именно:

 byte
  #0   #1   ...          #4   ...            #8 ....
+ - -+ - -+ - -+ - -+  +====+====+====+====+ - -+ - -+ - -+ - -+
: 08 : 10 : 02 : 00 :  | 00 | 00 | 00 | 60 | 66 : 66 : 0A : 40 : ....
+ - -+ - -+ - -+ - -+  +====+====+====+====+ - -+ - -+ - -+ - -+
 address of "%d"        ~~~~~~~~~~~~~~~~~~~
                        this, as an 'int'

поэтому printf получит 0x60000000 и отобразит его как десятичное целое число, которое равно 1610612736, поэтому вы видите этот результат. Другие числа можно объяснить аналогичным образом.

3.3 → ... 60000000 = 1610612736
3.4 → ... 40000000 = 1073741824
3.5 → ... 00000000 = 0
3.6 → ... C0000000 = -1073741824 (note 2 complement)
4   → ... 00000000 = 0
5   → ... 00000000 = 0

Ответ 2

Я предполагаю, что другие ответы, опубликованные так далеко, не имеют смысла: я думаю, вы намеренно используете разные конверсии для сканирования и печати и хотите понять результаты. Если вы действительно ошиблись, вы можете проигнорировать мой ответ.

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

Ответ 3

Спецификатор преобразования d, который вы используете во втором операторе printf, требует аргумента типа int. Ваш аргумент a после продвижения аргумента по умолчанию C имеет тип double. Передавая аргумент другого типа, который ожидается, является поведением undefined и, как обычно, с поведением undefined, все может случиться.

Ответ 4

Если вы хотите точно знать, что происходит, попробуйте printf('0x%08x\n', a); вместо printf("%d",a);. Вы сможете увидеть фактические биты переменной a вместо того, что дает вам printf("%d",a);.

Ответ 5

просто потому, что printf ("%d",a);: подумайте, что память в является int, поэтому интерпретирует ее содержимое как int. и printf("%f",a); рассмотрим содержимое памяти a как float, которое действительно...

но если вы пишете printf("%d",(int)a);//a преобразуется в int (by (int) cast с усечением). поэтому печатается приблизительное значение a.

Ответ 6

В C поплавки, переданные в качестве аргументов в функции с переменным числом аргументов, получают до удвоения. Поэтому в ссылке на строку формата функции printf вы не увидите разных спецификаторов формата для float и для парных. Таким образом, ваш "a" преобразуется из 32-битного float в 64-разрядный double при передаче в printf. Так получилось, что 4 и 5 представлены в виде двойников таким образом, что 32 из 64 бит являются нулями, и эти нулевые биты являются теми, которые функция printf интерпретирует как целое число, поскольку вы сказали ему распечатать целое число.

Ответ 7

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

int printf(const char *format, ...);

Таким образом, код printf(), вероятно, будет написан следующим образом: stdarg.h.

int printf(const char *format, ...) {
    va_list ap;
    char *p, *sval;
    int ival;
    float fval;

    va_start(ap, format);
    for(p=format; *p ; p++) {
        if (*p != '%') {
            putchar(*p);
            continue;
        }
        switch(*++p) {
            case 'd':
                ival = va_arg(ap, int);
                break;

            case 'f':
                fval = va_arg(ap, float);
                break;

            case 's':
                for (sval = va_arg(ap, char *); *sval; sval++);
                break;

            default:
                putchar(*p);
                break;
        }
    }
    va_end(ap);
}

Итак, если вы пройдете %d для float, тогда вы сможете выяснить, что произойдет внутри printf(). printf() будет интерпретировать переменную float как int, и это поведение undefined!

Надеюсь, это поможет!