C: Приведение минимального 32-битного целого числа (-2147483648) к float дает положительное число (2147483648.0)
Я работал над встроенным проектом, когда я столкнулся с чем-то, что, по моему мнению, было странным поведением. Мне удалось воспроизвести его на кодовом коде (см. Ниже), чтобы подтвердить, но у меня нет других компиляторов C на моей машине, чтобы попробовать их на них.
Сценарий: у меня есть #define
для наиболее отрицательного значения, которое может содержать 32-разрядное целое число, а затем я пытаюсь использовать его для сравнения со значением с плавающей запятой, как показано ниже:
#define INT32_MIN (-2147483648L)
void main()
{
float myNumber = 0.0f;
if(myNumber > INT32_MIN)
{
printf("Everything is OK");
}
else
{
printf("The universe is broken!!");
}
}
Codepad link: http://codepad.org/cBneMZL5
Мне кажется, что этот код должен работать нормально, но, к моему удивлению, он выводит The universe is broken!!
.
Этот код неявно передает INT32_MIN
в float
, но оказывается, что это приводит к значению с плавающей запятой 2147483648.0
(положительный!), хотя тип с плавающей точкой отлично способен представлять -2147483648.0
.
Кто-нибудь может понять причину такого поведения?
РЕШЕНИЕ КОДОВ. Как сказал Стив Джессоп в своем ответе, limits.h
и stdint.h
уже содержат правильный (рабочий) int
диапазон define
, поэтому я теперь использую их моего собственного #define
ПРОБЛЕМА/РЕШЕНИЕ ОБЪЯСНЕНИЯ РЕЗЮМЕ: Учитывая ответы и дискуссии, я думаю, что это хорошее резюме того, что происходит (обратите внимание: все еще читайте ответы/комментарии, потому что они дают более подробное объяснение)
- Я использую компилятор C89 с 32-разрядным
long
s, поэтому любые значения, превышающие LONG_MAX
и меньшие или равные ULONG_MAX
, за которыми следует постфикс L
, имеют тип unsigned long
-
(-2147483648L)
на самом деле является унарным -
на значении unsigned long
(см. предыдущую точку): -(2147483648L)
. Эта операция отрицания "обертывает" значение вокруг значения unsigned long
2147483648
(поскольку 32-разрядный unsigned long
имеет диапазон 0
- 4294967295
).
- Этот номер
unsigned long
выглядит как ожидаемое отрицательное значение int
, когда он печатается как int
или передается функции, потому что он сначала получает отличное от int
, которое обертывает это вне -range 2147483648
вокруг -2147483648
(поскольку 32-разрядный int
имеет диапазон от -2147483648 до 2147483647)
- Приведение в
float
, однако, использует фактическое значение unsigned long
2147483648
для преобразования, что приводит к значению с плавающей запятой 2147483648.0
.
Ответы
Ответ 1
В C89 с 32-битным long
, 2147483648L
имеет тип unsigned long int
(см. 3.1.3.2 Целочисленные константы). Поэтому, когда модульная арифметика применяется к унарной минусовой операции, INT32_MIN
- это положительное значение 2147483648 с типом unsigned long
.
В C99 2147483648L
имеет тип long
, если long
больше 32 бит или long long
в противном случае (см. 6.4.4.1 Целочисленные константы). Таким образом, проблем нет, а INT32_MIN
- отрицательное значение -2147483648 с типом long
или long long
.
Аналогично в C89 с long
больше 32 бит, 2147483648L
имеет тип long
и INT32_MIN
отрицательный.
Я предполагаю, что вы используете компилятор C89 с 32-разрядным long
.
Один из способов взглянуть на это - это то, что C99 исправляет "ошибку" на C89. В C99 десятичный литерал без суффикса U
всегда имеет тип подписи, тогда как в C89 он может быть подписан или без знака в зависимости от его значения.
Что вы, вероятно, должны сделать, btw, включает limits.h
и используйте INT_MIN
для минимального значения int
и LONG_MIN
для минимального значения a long
. Они имеют правильное значение, а ожидаемый тип (INT_MIN
- это int
, LONG_MIN
- long
). Если вам нужен точный 32-битный тип (при условии, что ваша реализация - 2 дополнения):
- для кода, который не должен быть портативным, вы можете использовать любой тип, который вы предпочитаете, правильный размер, и утверждать, что он находится в безопасности.
- для кода, который должен быть переносимым, найдите версию заголовка C99
stdint.h
, которая работает на вашем компиляторе C89, и используйте int32_t
и INT32_MIN
.
- Если все остальное не работает, напишите
stdint.h
самостоятельно и используйте выражение в ответе WiSaGaN. Он имеет тип int
, если int
составляет не менее 32 бит, в противном случае long
.
Ответ 2
Заменить
#define INT32_MIN (-2147483648L)
с
#define INT32_MIN (-2147483647 - 1)
-2147483648
интерпретируется компилятором как отрицание 2147483648
, что вызывает переполнение на int
. Поэтому вы должны написать вместо (-2147483647 - 1)
.
Тем не менее, это все C89
. См. Ответ Стива Джессопа за C99
.
Кроме того, long
обычно 32 бит на 32-битных машинах и 64 бит на 64-битных машинах. int
здесь все сделано.