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 здесь все сделано.