Почему abs (0x80000000) == 0x80000000? - PullRequest
21 голосов
/ 29 марта 2010

Я только начал читать Восторг хакера , и он определяет абс (-2 31 ) как -2 31 . Почему это так?

Я пытался printf("%x", abs(0x80000000)) на нескольких разных системах, и я получаю 0x80000000 на всех них.

Ответы [ 9 ]

42 голосов
/ 29 марта 2010

На самом деле, в C, поведение не определено. Из стандарта C99, § 7.20.6.1 / 2:

Функции abs, labs и llabs вычисляют абсолютное значение целого числа j. Если результат не может быть представлен, поведение не определено.

и его сноска:

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

14 голосов
/ 29 марта 2010

Для 32-битного типа данных нет выражения + 2 ^ 31, поскольку наибольшее число - 2 ^ 31-1 ... подробнее о дополнении к two ...

10 голосов
/ 29 марта 2010

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

То есть (в .NET, но все еще применяется):

int.MaxValue + 1 == int.MinValue  // Due to overflow.

И

Math.Abs((long)int.MinValue) == (long)int.MaxValue + 1
8 голосов
/ 30 марта 2010

Очевидно, математически, | & minus; 2 31 | составляет 2 31 . Если у нас есть 32 бита для представления целых чисел, мы можем представить не более 2 32 чисел. Если нам нужно представление, симметричное относительно 0, нам нужно принять несколько решений.

Для следующего, как и в вашем вопросе, я предполагаю 32-битные числа. По крайней мере, один битовый шаблон должен быть использован для 0. Таким образом, мы получим 2 32 & plusmn; 1 или менее битовых шаблонов для остальных чисел. Это число нечетное, поэтому мы можем либо иметь представление, которое не совсем симметрично относительно нуля, либо иметь одно число с двумя разными представлениями.

  • Если мы используем представление знак-величина , старший значащий бит представляет знак числа, а остальные биты представляют величину числа. В этой схеме 0x80000000 - это «отрицательный ноль» (то есть ноль), а 0x00000000 - это «положительный ноль» или обычный ноль. В этой схеме самое положительное число - 0x7fffffff (2147483647), а самое отрицательное число - 0xffffffff (& минус; 2147483647). Эта схема имеет то преимущество, что нам легко «декодировать», и она симметрична. Эта схема имеет недостаток, заключающийся в том, что вычисление a + b, когда a и b имеют разные знаки, является особым случаем и требует особого внимания.
  • Если мы используем представление добавок , самый значимый бит по-прежнему представляет знак. Положительные числа имеют этот бит как 0, а остальные биты составляют величину числа. Для отрицательных чисел вы просто инвертируете биты из соответствующего представления положительного числа (возьмите дополнение с длинной серией единиц - отсюда и имя ones ', дополнение ). В этой схеме максимальное положительное число все еще равно 0x7fffffff (2147483647), а максимальное отрицательное число равно 0x80000000 (& минус; 2147483647). Снова есть два представления 0: положительный ноль равен 0x00000000, а отрицательный ноль равен 0xffffffff. Эта схема также имеет проблемы с расчетами с использованием отрицательных чисел.
  • Если мы используем схему дополнения до двух , отрицательные числа получаются путем взятия представления дополнения и добавления к нему 1. В этой схеме есть только один 0, а именно 0x00000000. Наиболее положительное число - 0x7fffffff (2147483647), а наиболее отрицательное число - 0x80000000 (& минус; 2147483648). В этом представлении есть асимметрия. Преимущество этой схемы в том, что не нужно иметь дело с особыми случаями для отрицательного числа. Представление заботится о том, чтобы дать вам правильный ответ, если результат не переполняется. По этой причине большая часть текущего оборудования представляет целые числа в этом представлении.

В представлении дополнения до двух невозможно представить 2 31 . Фактически, если вы посмотрите на limits.h вашего компилятора или эквивалентный файл, вы можете увидеть определение для INT_MIN таким образом:

#define INT_MIN (-2147483647 - 1)

Это сделано, а не

#define INT_MIN -2147483648

потому что 2147483648 слишком велик, чтобы поместиться в int в 32-битном представлении дополнения до двух. К тому времени, когда унарный оператор минус «получает» номер для работы, уже слишком поздно: переполнение уже произошло, и вы не можете это исправить.

Итак, чтобы ответить на ваш исходный вопрос, абсолютное значение наиболее отрицательного числа в представлении дополнения до двух не может быть представлено в этой кодировке. Кроме того, из вышесказанного, чтобы перейти от отрицательного значения к положительному значению в представлении дополнения до двух, вы берете его дополнение и затем добавляете 1. Итак, для 0x80000000:

1000 0000 0000 0000 0000 0000 0000 0000   original number
0111 1111 1111 1111 1111 1111 1111 1111   ones' complement
1000 0000 0000 0000 0000 0000 0000 0000   + 1

вы получите исходный номер обратно.

3 голосов
/ 29 марта 2010

Представление числа дополнения до двух имеет старший бит в виде отрицательного числа. 0x80000000 - это 1, за которым следуют 31 ноль, первая 1 представляет -2 ^ 31, а не 2 ^ 31. Следовательно, невозможно представить 2 ^ 31, поскольку наибольшее положительное число равно 0x7FFFFFFF, за которым следует 0, за которым следует 31, что равно 2 ^ 31-1.

abs (0x80000000) поэтому не определен в дополнении к двум, поскольку он слишком большой, из-за этого машина просто сдается и снова дает вам 0x80000000. Обычно, по крайней мере.

3 голосов
/ 29 марта 2010

Это восходит к тому, как хранятся числа.

Отрицательные числа сохраняются с использованием дополнения до двух. Алгоритм идет как ...

Отразить все биты, затем добавить 1.

Использование восьмибитных чисел для примеров ...

+ 0 = -0

00000000 -> 11111111, 11111111 + 1 = 100000000

(но из-за ограничения битов это становится 00000000).

И ...

-128 [aka - (2 ^ 7)] равно - (- 128)

10000000 -> 01111111, 01111111 + 1 = 10000000

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

1 голос
/ 29 марта 2010

0x8000 .. сохраняется как 10000 .... (двоичный). Это называется дополнением до двух, что означает, что старший бит (тот, что слева) используется для хранения знака значения, а отрицательные значения хранятся с отрицательным двоичным кодом - 1. Функция abs () теперь проверяет бит знака, видит, что оно установлено и вычисляет положительное значение.

  • Чтобы получить положительное значение первым отменяет все биты в переменной, в результате чего 01111 ...
  • Затем добавляет 1, что снова приводит к 1000 ... 0x8000 ... мы начали с

Теперь это опять отрицательное число, которое нам не нужно, причина в переполнении, попробуйте число 0x9000 ..., которое равно 10010 ...

  • отрицание битов приводит к 01101 ... добавление одного результата в 01110 ...
  • 0xE000 ... положительное число

С этим номером переполнение останавливается нулевым битом справа

1 голос
/ 29 марта 2010

Я думаю, что способ abs - это сначала проверить sign bit числа. Если его очистить, ничего не делать, так как номер уже +ve, иначе верните 2's complement номера. В вашем случае это число -ve, и нам нужно найти его 2's complement. Но дополнением 2 к 0x80000000 оказывается 0x80000000.

0 голосов
/ 02 апреля 2010

, потому что он использует инструкцию neg для выполнения этой операции.

В книге по программированию на языке Искусства Ассамблеи они так говорили.

Если операнд равен нулю, его знак не изменить, хотя это очищает нести флаг. Отрицание любого другого значения устанавливает флаг переноса. Отрицание байта содержащий -128, слово, содержащее -32,768, или двойное слово, содержащее -2,147,483,648, не меняет операнд, но устанавливает переполнение флаг. Нег всегда обновляет A, S, P, и флаги Z, как будто вы используете подчиненная инструкция

источник: http://www.arl.wustl.edu/~lockwood/class/cs306/books/artofasm/Chapter_6/CH06-2.html#HEADING2-313 Так что он установит флаг переполнения и будет молча. Вот в чем причина.

...