Подтвердить что ты не робот

Почему - (- 2147483648) = - 2147483648 в 32-битной машине?

Я думаю, что вопрос сам по себе объясняет, я думаю, он, вероятно, имеет какое-то отношение к переполнению, но все же я не совсем понимаю его. Что происходит, побитовое, под капотом?

Почему -(-2147483648) = -2147483648 (по крайней мере, при компиляции в C)?

4b9b3361

Ответ 1

Отрицание целочисленной константы (unsuffixed):

Выражение -(-2147483648) отлично определено в C, однако может быть неясно, почему именно так.

Когда вы пишете -2147483648, он формируется как унарный оператор минус, примененный к целочисленной константе. Если 2147483648 не может быть выражено как int, то s представляется как long или long long * (в зависимости от того, что подходит первым), где последний тип гарантируется C Стандарт для покрытия этого значения .

Чтобы подтвердить это, вы можете изучить его:

printf("%zu\n", sizeof(-2147483648));

который дает 8 на моей машине.

Следующий шаг - применить второй оператор -, и в этом случае конечное значение 2147483648L (предполагается, что оно было в конечном итоге представлено как long). Если вы попытаетесь присвоить его объекту int следующим образом:

int n = -(-2147483648);

тогда фактическое поведение определяется реализацией. Ссылаясь на стандарт:

C11 §6.3.1.3/3 Целочисленные и беззнаковые целые числа

В противном случае новый тип подписан и значение не может быть представлено в этом; либо результат определяется реализацией или сигнал, определяемый реализацией.

Самый распространенный способ - просто отключить более высокие бит. Например, GCC документы это как:

Для преобразования в тип ширины N значение уменьшается по модулю 2 ^ N быть в пределах диапазона типа; сигнал не поднимается.

Понятно, что преобразование в тип ширины 32 может быть проиллюстрировано побитовой операцией AND:

value & (2^32 - 1) // preserve 32 least significant bits

В соответствии с двумя дополнительными арифметическими, значение n формируется со всеми нулями и битом MSB (sign), который представляет значение -2^31, то есть -2147483648.

Отрицание объекта int:

Если вы попытаетесь отрицать объект int, который содержит значение -2147483648, тогда, предположив, что используется две машины дополнения, программа будет демонстрировать поведение undefined:

n = -n; // UB if n == INT_MIN and INT_MAX == 2147483647

C11 §6.5/5 Выражения

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

Дополнительные ссылки:


*) В снятом стандарте C90 не было типа long long, и правила были разными. В частности, последовательность для unsuffixed decimal была int, long int, unsigned long int (C90 §6.1.3.2 Целочисленные константы).

†) Это связано с LLONG_MAX, которое должно быть не менее +9223372036854775807 (C11 §5.2.4.2.1/1).

Ответ 2

Примечание: этот ответ не применяется как таковой на устаревшем стандарте ISO C90, который все еще используется многими компиляторами

Прежде всего, на C99, C11 выражение -(-2147483648) == -2147483648 фактически ложно:

int is_it_true = (-(-2147483648) == -2147483648);
printf("%d\n", is_it_true);

печатает

0

Итак, как возможно, что это оценивается как истина? Аппарат использует 32-битные два дополнения целые числа. 2147483648 представляет собой целочисленную константу, которая не соответствует 32 битам, поэтому она будет либо long int, либо long long int в зависимости от того, какой из них является первым, где он подходит. Это отрицание приведет к -2147483648 - и снова, даже если число -2147483648 может поместиться в 32-битное целое число, выражение -2147483648 состоит из > 32-битного положительного целого числа, которому предшествует унарный -!

Вы можете попробовать следующую программу:

#include <stdio.h>

int main() {
    printf("%zu\n", sizeof(2147483647));
    printf("%zu\n", sizeof(2147483648));
    printf("%zu\n", sizeof(-2147483648));
}

Выход на такой машине, скорее всего, будет 4, 8 и 8.

Теперь -2147483648 negated снова приведет к +214783648, который по-прежнему имеет тип long int или long long int, и все в порядке.

В C99, C11, целочисленное константное выражение -(-2147483648) хорошо определено на всех соответствующих реализациях.


Теперь, когда этому значению присваивается переменная типа int, с 32-битным и двумя дополнительными представлениями, это значение не представляется в нем - значения в 32-битном дополнении 2 будут варьироваться от -2147483648 до 2147483647.

В стандарте C11 6.3.1.3p3 говорится следующее целочисленное преобразование:

  • [Когда] новый тип подписан и значение не может быть представлено в нем; либо результат определяется реализацией, либо выражается определенный реализацией.

То есть, стандарт C фактически не определяет, что такое значение в этом случае, или не исключает возможности прекращения выполнения программы из-за поднятого сигнала, но оставляет ее в реализациях (т.е. компиляторы), чтобы решить, как с ним справиться (C11 3.4.1):

поведение, определяемое реализацией

неопределенное поведение, когда каждая реализация документирует, как делается выбор

и (3.19.1):

значение, определяемое реализацией

неопределенное значение, где каждая реализация документирует, как делается выбор


В вашем случае поведение, определяемое реализацией, заключается в том, что это 32 бита младшего разряда [*]. Из-за 2-х дополнений (long) long int value 0x80000000 имеет бит 31, а все остальные бит очищены. В 32-битных двух дополнительных целых числах бит 31 является битом знака - это означает, что число отрицательно; все биты с нулевыми значениями означают, что это минимальное представляемое число, т.е. INT_MIN.


[*] GCC документирует свое поведение, определяемое реализацией, в этом случае следующим образом:

Результат или сигнал, вызываемый преобразованием целого числа в целочисленный тип со знаком, если значение не может быть представлено в объекте этого типа (C90 6.2.1.2, C99 и C11 6.3.1.3).

Для преобразования в тип ширины N значение уменьшается по модулю 2^N, чтобы находиться в пределах диапазона типа; сигнал не поднимается.

Ответ 3

Это не вопрос C, поскольку для реализации C с 32-битным представлением двух дополнений для типа int эффект применения унарного оператора отрицания к int, имеющему значение -2147483648, > undefined. То есть, язык C специально отклоняет обозначение результата оценки такой операции.

Рассмотрим в более общем смысле, как унарный оператор - определен в двух арифметических дополнениях: обратный положительный номер x формируется путем переброса всех битов его двоичного представления и добавления 1. Это же определение служит также для любого отрицательного числа, которое имеет по крайней мере один бит, отличный от установленного битового знака.

Тем не менее, возникают незначительные проблемы для двух чисел, для которых нет битов значений: 0, у которого нет битов, установленных на всех, и числа, которое имеет только свой битовый бит (-2147483648 в 32-битном представлении). Когда вы переворачиваете все биты любого из них, вы получаете все биты значений. Поэтому, когда вы впоследствии добавляете 1, результат переполняет биты значений. Если вы представляете себе выполнение добавления, как если бы число было неподписанным, обработка бита знака как бит значения, то вы получаете
    -2147483648 (decimal representation)
-->  0x80000000 (convert to hex)
-->  0x7fffffff (flip bits)
-->  0x80000000 (add one)
--> -2147483648 (convert to decimal)

Аналогично относится к инвертированию нуля, но в этом случае переполнение при добавлении 1 также переполняет ранний знаковый бит. Если переполнение игнорируется, результирующие 32 младших бита равны нулю, поэтому -0 == 0.

Ответ 4

Я собираюсь использовать 4-битное число, просто чтобы сделать математику простой, но идея такая же.

В 4-битовом номере возможные значения находятся между 0000 и 1111. Это будет от 0 до 15, но если вы хотите представить отрицательные числа, первый бит используется для обозначения знака (0 для положительного и 1 для отрицательный).

Итак, 1111 не 15. Когда первый бит равен 1, это отрицательное число. Чтобы узнать его значение, мы используем метод с двумя дополнениями, как уже описано в предыдущих ответах: "инвертировать биты и добавить 1":

  • инвертирование бит: 0000
  • добавление 1: 0001

0001 в двоичном выражении равно 1 в десятичном значении, поэтому 1111 равно -1.

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

Теперь давайте посмотрим 1000. Первый бит равен 1, поэтому это отрицательное число. Используя метод с двумя дополнениями:

  • инвертировать биты: 0111
  • добавить 1:1000 (8 в десятичной форме)

Так 1000 - -8. Если мы делаем -(-8), то в двоичном выражении это означает -(1000), что на самом деле означает использование метода с двумя дополнениями в 1000. Как мы видели выше, результат также 1000. Итак, в 4-битном номере -(-8) равно -8.

В 32-битном номере -2147483648 в двоичном формате 1000..(31 zeroes), но если вы используете метод с двумя дополнениями, вы получите одно и то же значение (результат будет таким же числом).

Вот почему в 32-битном номере -(-2147483648) равен -2147483648

Ответ 5

Это зависит от версии C, особенностей реализации и того, говорим ли мы о значениях переменных или литералов.

Первое, что нужно понять, это отсутствие отрицательных целых литералов в C "-2147483648" - это унарная минусовая операция, за которой следует положительный целочисленный литерал.

Предположим, что мы работаем на типичной 32-битной платформе, где int и long являются 32 битами и длинными длинными 64 бит и рассматривают выражение.

(- (- 2147483648) == -2147483648)

Компилятор должен найти тип, который может содержать 2147483648, на компиляционном компиляторе C99 он будет использовать тип "long long", но компилятор C90 может использовать тип "unsigned long".

Если компилятор использует тип long long, то ничего не переполняется, а сравнение - false. Если компилятор использует unsigned long, тогда действуют беззнаковые правила wraparound, и сравнение истинно.

Ответ 6

По той же причине, что намотка счетчика кассетной деки 500 шагов вперед от 000 (до 001 002 003...) покажет 500, а намотка назад на 500 шагов назад от 000 (до 999 998 997...) будет также показывают 500.

Это двухкомпонентная нотация. Разумеется, поскольку 2-значное соглашение о согласии заключается в том, чтобы рассмотреть самый верхний бит знакового бита, результат переполняет представляемый диапазон, так же как 2000000000 + 2000000000 переполняет представленный диапазон.

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