Проблемы с приведением NAN float к int

Игнорируя, почему я хотел бы это сделать, стандарт 754 IEEE fp не определяет поведение для следующего:

float h = NAN;
printf("%x %d\n", (int)h, (int)h);

Gives: 80000000 -2147483648

В основном, независимо от того, какое значение NAN я даю, он выводит 80000000 (шестнадцатеричное) или -2147483648 (десятичное). Есть ли причина для этого и / или это правильное поведение? Если да, то почему?

Я даю ему разные значения NaN: Как вручную установить битовое значение числа с плавающей запятой, равное NaN?

Итак, в основном, бывают ли случаи, когда полезная нагрузка NaN влияет на вывод приведения?

Спасибо!


person Chris    schedule 28.04.2012    source источник
comment
+1 потому что взлом приводит к лучшему пониманию   -  person Israel Unterman    schedule 28.04.2012


Ответы (3)


Результат преобразования числа с плавающей запятой в целое число не определен / не указан для значений, не входящих в диапазон целочисленной переменной (± 1 для усечения).

Пункт 6.3.1.4:

Когда конечное значение реального типа с плавающей запятой преобразуется в целочисленный тип, отличный от _Bool, дробная часть отбрасывается (т. Е. Значение обрезается до нуля). Если значение составной части не может быть представлено целочисленным типом, поведение не определено.

Если реализация определяет __STDC_IEC_559__, то для преобразований из типа с плавающей запятой в целочисленный тип, отличный от _BOOL:

если значение с плавающей запятой бесконечно или NaN, или если целая часть значения с плавающей запятой превышает диапазон целочисленного типа, возникает «недопустимое» исключение с плавающей запятой и результирующее значение не указано.

(Приложение F [нормативное], пункт 4.)

Если реализация не определяет __STDC_IEC_559__, тогда все ставки отключены.

person Daniel Fischer    schedule 28.04.2012
comment
Учитывая тот факт, что поведение не определено, является ли результат, который я получил, общим для этого поведения undefined? то есть кто-нибудь знает о системе, если бы у меня было другое поведение, чем это? В спецификации 754 говорится, что поведение операций NaN заключается в том, что полезная нагрузка должна передаваться. - person Chris; 28.04.2012
comment
Мне неизвестна реализация, которая работала бы иначе, но я не знаком ни с чем, кроме gcc. gcc производит INT_MIN для всех преобразований вне допустимого диапазона в int, насколько мне известно (но это тоже очень мало). - person Daniel Fischer; 28.04.2012
comment
Я почти уверен, что вы имеете в виду gcc на x86. Нет причин предполагать, что результат должен быть одинаковым везде; это скорее всего артефакт поведения fpu. - person R.. GitHub STOP HELPING ICE; 29.04.2012
comment
О, хм, румянец, конечно. Но, конечно же, оборудование, отличное от x86, - это миф, изобретенный Apple, чтобы продавать больше компьютеров Mac. (Спасибо за исправление, @R ..) - person Daniel Fischer; 29.04.2012
comment
Я думал, что Google придумал миф о продаже телефонов. ;-) - person R.. GitHub STOP HELPING ICE; 29.04.2012
comment
Сам GCC даст вам 0 при преобразовании NAN в int во время компиляции (а не во время выполнения, где вы получите INT_MIN). Таким образом, даже на одной платформе вы можете получить два разных значения в зависимости от того, смог ли компилятор определить вашу NAN как постоянное значение. - person John Zwinck; 11.08.2014
comment
Результат не является неопределенным, но получается неопределенное значение (см. ISO C17, F.4). С GCC 4.6 в магистраль (снимок 10.0.0) под Linux / x86_64 (Debian / нестабильный) я получаю для преобразований из volatile double NAN в int, unsigned int, long, unsigned long: INT_MIN, 0, LONG_MIN, LONG_MAX + 1 соответственно ( последние два значения имеют одинаковое представление, но это не относится к результатам int и unsigned int). - person vinc17; 27.01.2020
comment
@ vinc17 Спасибо за предупреждение. Оказывается, это уже имело место в C11, но я не читал приложение и следовал только тому, что было заявлено в 6.3.1.4. - person Daniel Fischer; 27.01.2020

У такого поведения есть причина, но обычно не стоит полагаться на нее.

Как вы заметили, IEEE-754 не определяет, что происходит, когда вы конвертируете NaN с плавающей запятой в целое число, за исключением того, что он должен вызывать исключение недопустимой операции, которое ваш компилятор, вероятно, игнорирует. Стандарт C говорит, что поведение не определено, что означает, что вы не только не знаете, какой целочисленный результат получите, но и не знаете, что будет делать ваша программа; стандарт позволяет программе прерывать работу, получать сумасшедшие результаты или делать что-нибудь. Вы, вероятно, выполнили эту программу на процессоре Intel, и ваш компилятор, вероятно, выполнил преобразование с помощью одной из встроенных инструкций. Intel очень тщательно определяет поведение инструкций, и поведение для преобразования NaN с плавающей запятой в 32-битное целое должно возвращать 0x80000000, независимо от полезной нагрузки NaN, что вы и наблюдали.

Поскольку Intel определяет поведение инструкции, вы можете положиться на нее, если знаете, какая инструкция используется. Однако, поскольку компилятор не предоставляет вам таких гарантий, вы не можете полагаться на использование этой инструкции.

person Eric Postpischil    schedule 01.05.2012
comment
Возможно, процессоры Intel преобразуют NAN в 32-битное int как 0x80000000, но это не поможет вам, если ваша NAN является постоянным значением, определенным вашим компилятором. В таких случаях вы можете увидеть значения, отличные от INT_MIN, потому что преобразование выполняется во время компиляции, а не во время выполнения, поэтому семантика Intel x86 никогда не вступает в игру. Например, когда GCC преобразует NAN в int во время компиляции, он дает 0. - person John Zwinck; 11.08.2014
comment
Соответствующая инструкция felixcloutier.com/x86/cvttsd2si. Целочисленное неопределенное значение x86 - MSB = 1, rest = 0, то есть INT_MIN или INT64_MIN. Как вы говорите, другое использование инструкции может иметь разные результаты, например float - ›uint32_t на x86-64 часто преобразуется в int64_t и берет младшую половину, потому что это в основном бесплатно в asm, а x86 (до AVX-512) не предоставляет FP -› беззнаковых преобразований напрямую. (C не определяет поведение отрицательного FP - ›unsigned; модульное сокращение только для широкого целочисленного типа -› unsigned). - person Peter Cordes; 12.04.2021
comment
Как вы говорите, другие ISA могут быть разными, например преобразование без знака в C работает должным образом на x86, но не на ARM - person Peter Cordes; 12.04.2021

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

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

Однако мне есть что сказать о вашем тестировании.

float h = NAN;
printf("%x %d\n", (int)h, (int)h);

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

printf("%x %d\n", *(int *)&h, *(int *)&h);

То есть вы берете адрес числа с плавающей запятой, затем называете его указателем на int и в конечном итоге принимаете значение int. Таким образом, битовое представление сохраняется.

person Israel Unterman    schedule 28.04.2012
comment
привет @Israel printf("%x %d\n", *(int *)&h, *(int *)&h);, это хороший способ получить битовое представление адреса, есть ли способ записать обратно битовое представление в адрес? скажем, напишите 0x7ff8000000000000 в &h? - person hukeping; 17.01.2020