Сдвиг вправо отрицательных чисел в C - PullRequest
25 голосов
/ 07 декабря 2009

У меня есть код C, в котором я делаю следующее.

int nPosVal = +0xFFFF;   // + Added for ease of understanding
int nNegVal = -0xFFFF;   // - Added for valid reason

Теперь, когда я пытаюсь

printf ("%d %d", nPosVal >> 1, nNegVal >> 1);

Я получаю

32767 -32768

Ожидается ли это?

Я могу думать что-то вроде

65535 >> 1 = (int) 32767.5 = 32767
-65535 >> 1 = (int) -32767.5 = -32768

То есть -32767,5 округляется до -32768.

Правильно ли это понимание?

Ответы [ 6 ]

38 голосов
/ 07 декабря 2009

Похоже, что ваша реализация, вероятно, выполняет арифметический сдвиг битов с двумя числами дополнения. В этой системе он сдвигает все биты вправо, а затем заполняет верхние биты копией того, что было последним битом. Итак, для вашего примера, трактуя int как 32-битный здесь:

nPosVal = 00000000000000001111111111111111
nNegVal = 11111111111111110000000000000001

После смены у вас есть:

nPosVal = 00000000000000000111111111111111
nNegVal = 11111111111111111000000000000000

Если вы преобразуете это обратно в десятичное число, вы получите 32767 и -32768 соответственно.

По сути, сдвиг вправо округляет в сторону отрицательной бесконечности.

Редактировать: В соответствии с разделом 6.5.7 последнего проекта стандарта , это поведение для отрицательных чисел зависит от реализации:

Результат E1 >> E2 равен E1 сдвинутые вправо битовые позиции E2. Если E1 имеет тип без знака или если E1 имеет тип со знаком и неотрицательное значение, значение результата является неотъемлемой частью отношения E1 / 2 E2 . Если E1 имеет тип со знаком и отрицательное значение, результирующее значение определяется реализацией.

Они заявили рационально для этого:

Комитет C89 подтвердил свободу в реализации, предоставленную K & R, в том, что она не требует подписанной операции сдвига вправо для подписи расширения, поскольку такое требование может замедлить быстрый код и поскольку полезность знака расширенных сдвигов является предельной. (Перенос отрицательного дополнения до двух целое арифметически правильное одно место - , а не - это то же самое, что деление на два!)

Так что это зависит от реализации в теории. На практике я никогда не видел, чтобы реализация , а не выполняла арифметическое смещение вправо, когда левый операнд подписан.

18 голосов
/ 07 декабря 2009

Нет, вы не получите дробные числа, такие как 0,5, при работе с целыми числами. Результаты можно легко объяснить, если взглянуть на двоичные представления двух чисел:

      65535: 00000000000000001111111111111111
     -65535: 11111111111111110000000000000001

Бит смещается вправо на один бит и расширяется слева (обратите внимание, что это зависит от реализации, спасибо Тренту):

 65535 >> 1: 00000000000000000111111111111111
-65535 >> 1: 11111111111111111000000000000000

Преобразование обратно в десятичное число:

 65535 >> 1 = 32767
-65535 >> 1 = -32768
7 голосов
/ 07 декабря 2009

Спецификация C не определяет, сдвигается ли знаковый бит или нет. Это зависит от реализации.

3 голосов
/ 07 декабря 2009

A-1: ​​Да. 0xffff >> 1 - это 0x7fff или 32767. Я не уверен, что делает -0xffff. Это своеобразно.

A-2: Сдвиг - это не то же самое, что деление. Это сдвиг битов - примитивная двоичная операция. То, что иногда оно может использоваться для некоторых типов деления, удобно, но не всегда одинаково.

3 голосов
/ 07 декабря 2009

Когда вы сдвигаете вправо, младший бит сбрасывается.

0xFFFF = 0 1111 1111 1111 1111, сдвиг вправо для получения 0 0111 1111 1111 1111 = 0x7FFF

-0xFFFF = 1 0000 0000 0000 0001 (дополнение 2 с), которое смещается вправо до 1 1000 0000 0000 0000 = -0x8000

2 голосов
/ 07 декабря 2009

Ниже уровня C машины имеют ядро ​​ЦП, которое является целым числом или скаляр . Хотя в наши дни каждый настольный процессор имеет FPU, это не всегда было так, и даже сегодня встроенные системы создаются без инструкций с плавающей запятой.

Современные парадигмы программирования, а также конструкции и языки ЦП относятся к эпохе, когда FPU может даже не существовать.

Таким образом, инструкции CPU реализуют операции с фиксированной точкой , которые обычно обрабатываются как чисто integer ops. Только если программа объявляет элементы float или double , будут существовать любые дроби. (Ну, вы можете использовать процессорные операции для «фиксированной точки» с дробями, но это сейчас и всегда было довольно редко.)

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

Чтобы продвинуться в вашем понимании, вам нужно исследовать "арифметику с двумя дополнениями".

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...