Целочисленное значение плавающего типа в C - PullRequest
10 голосов
/ 29 декабря 2011
#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

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

Ответы [ 7 ]

23 голосов
/ 29 декабря 2011

Функция 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, это приводит к неопределенному поведению (§7.21.6.1 / 8–9), что включает в себя возможность печати какого-то странного числа, конец истории.

Но что на самом деле происходит? На большинстве платформ double представлен в формате " IEEE 754 binary64 " и в формате float в binary32 .Числа, которые вы ввели, конвертируются в число с плавающей точкой, значение которого имеет только 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)

Теперь мы конвертируем это в двойное число, которое имеет 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's complement)
4   → ... 00000000 = 0
5   → ... 00000000 = 0
2 голосов
/ 29 декабря 2011

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

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

0 голосов
/ 29 декабря 2011

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, и это поведение не определено!

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

0 голосов
/ 29 декабря 2011

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

0 голосов
/ 29 декабря 2011

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

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

0 голосов
/ 29 декабря 2011

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

0 голосов
/ 29 декабря 2011

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

...