Сдвиг вправо отрицательных чисел в C
У меня есть код 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 ответов
Похоже, что ваша реализация, вероятно, выполняет арифметический сдвиг битов с двумя числами дополнения. В этой системе он сдвигает все биты вправо и затем заполняет верхние биты копией того, что было последним битом. Итак, для вашего примера, трактуя 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, в том, что она не требует подписанной операции сдвига вправо для подписи продления, поскольку такое требование может замедлить быстрый код, а полезность подписания расширенных сдвигов незначительна. (Перемещение отрицательного целого числа с двумя дополнительными арифметически в одно правильное место - это не то же самое, что деление на два!)
Так что это зависит от реализации в теории. На практике я никогда не видел, чтобы реализация не выполняла арифметическое смещение вправо, когда левый операнд подписан.
Нет, вы не получите дробные числа, такие как 0,5, при работе с целыми числами. Результаты можно легко объяснить, если взглянуть на двоичные представления двух чисел:
65535: 00000000000000001111111111111111
-65535: 11111111111111110000000000000001
Бит сдвигается вправо на один бит и расширяется слева (обратите внимание, что это зависит от реализации, спасибо Тренту):
65535 >> 1: 00000000000000000111111111111111
-65535 >> 1: 11111111111111111000000000000000
Преобразовать обратно в десятичное число:
65535 >> 1 = 32767
-65535 >> 1 = -32768
Спецификация C не указывает, сдвинут ли знаковый бит или нет. Это зависит от реализации.
А-1: Да. 0xffff >> 1 - это 0x7fff или 32767. Я не уверен, что делает -0xffff. Это своеобразно.
A-2: Сдвиг - это не то же самое, что деление. Это сдвиг битов - примитивная двоичная операция. То, что его иногда можно использовать для некоторых типов деления, удобно, но не всегда одинаково.
Когда вы сдвигаете вправо, младший бит сбрасывается.
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
Ниже уровня C машины имеют ядро ЦП, которое является целочисленным или скалярным. Хотя в наши дни каждый настольный процессор имеет FPU, это было не всегда так, и даже сегодня встроенные системы создаются без инструкций с плавающей запятой.
Сегодняшние парадигмы программирования, а также конструкции и языки процессоров относятся к эпохе, когда FPU может даже не существовать.
Таким образом, инструкции CPU реализуют операции с фиксированной запятой, обычно рассматриваемые как чисто целочисленные операции. Любые дроби будут существовать только в том случае, если программа объявляет элементы типа float или double. (Ну, вы можете использовать процессорные операции для "фиксированной точки" с дробями, но это сейчас и всегда было довольно редко.)
Независимо от того, что требовалось комитетом по языковым стандартам много лет назад, все разумные машины распространяют бит знака на правильные сдвиги чисел со знаком. Сдвиг вправо значений без знака смещается в нули слева. Биты, сдвинутые справа, падают на пол.
Чтобы углубить ваше понимание, вам нужно изучить "арифметику с двумя дополнениями".