Сдвиг вправо с нулями в начале

Я пытаюсь сделать вид левого сдвига, который бы добавлял нули в начале вместо единиц. Например, если я ушел из смены 0xffЯ получаю это:

0xff << 3 = 11111000

Однако, если я правильно переверну его, я получу это:

0xff >> 3 = 11111111

Есть ли какая-либо операция, которую я мог бы использовать, чтобы получить эквивалент сдвига влево? я хотел бы получить это:

00011111

Любое предложение?

редактировать

Чтобы ответить на комментарии, вот код, который я использую:

int number = ~0;
number = number << 4;
std::cout << std::hex << number << std::endl;

number = ~0;
number = number >> 4;
std::cout << std::hex << number << std::endl;

выход:

fffffff0
ffffffff

Поскольку кажется, что в целом это должно работать, меня интересует, почему этот конкретный код не работает. Любая идея?

7

Решение

Вот как работают C и двоичная арифметика:

Если вы вышли из смены 0xff << 3, вы получите двоичный файл: 00000000 11111111 << 3 = 00000111 11111000

Если вы сдвинете вправо 0xff >> 3, вы получите двоичный файл: 00000000 11111111 >> 3 = 00000000 00011111

0xff является (подписанным) int с положительным значением 255, Поскольку он положительный, результатом его изменения является четко определенное поведение как на C, так и на C ++. Он не будет делать никаких арифметических сдвигов, никакого рода или плохо определенного поведения.

#include <stdio.h>

int main()
{

printf("%.4X %d\n", 0xff << 3, 0xff << 3);
printf("%.4X %d\n", 0xff >> 3, 0xff >> 3);

}

Выход:

07F8 2040
001F 31

Итак, вы делаете что-то странное в вашей программе, потому что она не работает, как ожидалось. Возможно, вы используете переменные типа char или символьные литералы C ++.


Источник: ISO 9899: 2011 6.5.7.


РЕДАКТИРОВАТЬ после обновления вопроса

int number = ~0; дает вам отрицательное число, эквивалентное -1, при условии дополнения до двух.

number = number << 4; вызывает неопределенное поведение, так как вы оставили сдвиг отрицательного числа. Программа правильно реализует неопределенное поведение, поскольку она либо что-то делает, либо вообще ничего не делает. Он может напечатать fffffff0 или распечатать розового слона, или отформатировать жесткий диск.

number = number >> 4; вызывает поведение, определяемое реализацией. В вашем случае ваш компилятор сохраняет бит знака. Это известно как арифметический сдвиг, и арифметическое смещение вправо работает таким образом, что MSB заполняется любым битовым значением, которое он имел до сдвига. Поэтому, если у вас отрицательное число, вы почувствуете, что программа «сдвигается в единицу».

В 99% всех реальных случаев не имеет смысла использовать побитовые операторы для чисел со знаком. Поэтому всегда проверяйте, что вы используете числа без знака и что ни одно из опасных правил неявного преобразования в C / C ++ не преобразует их в числа со знаком (дополнительную информацию об опасных преобразованиях см. В «Правилах целочисленного продвижения» и «Обычные арифметические преобразования». «, много хорошей информации о тех, кто на SO).

РЕДАКТИРОВАТЬ 2, некоторая информация из обоснования документа C99 V5.10:

6.5.7 Битовые операторы сдвига

Описание операторов сдвига в K&R предполагает, что сдвиг на
длинный счет должен заставить левый операнд быть расширен задолго до
быть сдвинутым. Более интуитивная практика, одобренная C89
Комитет состоит в том, что тип числа смен не имеет никакого отношения к
тип результата.

Тихое изменение в C89

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

10

Другие решения

Если вы явно указали shift 0xff, он работает так, как вы ожидали

cout << (0xff >> 3) << endl; // 31

Это должно быть возможно только в том случае, если 0xff в типе ширины со знаком 8 (char а также signed char на популярных платформах).


Итак, в общем случае:

Вам нужно использовать неподписанные целые

(unsigned type)0xff

сдвиг вправо работает как деление на 2 (с округлением вниз, если я правильно понимаю).

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

7

Два вида сдвига вправо, о которых вы говорите, называются Логический сдвиг а также Арифметический сдвиг. C и C ++ используют логическое смещение для целых чисел без знака, и большинство компиляторов будут использовать арифметическое смещение для целого числа со знаком, но это не гарантируется стандартным значением, означающим, что значение смещения вправо со знаком минус int определяется реализацией.

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

5

Чтобы объяснить ваш реальный код, вам просто нужны версии цитат из стандарта C на языке C ++, которые Лундин дал в комментариях:

int number = ~0;
number = number << 4;

Неопределенное поведение. [expr.shift] говорит

Значение Е1 << E2 — E1-сдвинутые влево битовые позиции E2; освобождено
биты обнуляются. Если E1 имеет тип без знака, значение
результат E1 × 2E2, уменьшено по модулю на единицу больше максимального значения
представимый в типе результата. Иначе, если E1 имеет подписанный тип
и неотрицательное значение
, и E1 × 2E2 представима в результате
тип, то это итоговое значение; в противном случае поведение
недеформированной определены
.

number = ~0;
number = number >> 4;

Результат, определенный реализацией, в этом случае ваша реализация дала вам арифметический сдвиг:

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

Вы должны использовать тип без знака:

unsigned int number = -1;
number = number >> 4;
std::cout << std::hex << number << std::endl;

Выход:

0x0fffffff
3

Чтобы добавить мои 5 центов стоит здесь …
Я сталкиваюсь точно с той же проблемой, что и this.lau! Я провел некоторые поверхностные исследования по этому вопросу, и вот мои результаты:

typedef unsigned int Uint;
#define U31 0x7FFFFFFF
#define U32 0xFFFFFFFF

printf ("U31 right shifted: 0x%08x\n", (U31 >> 30));
printf ("U32 right shifted: 0x%08x\n", (U32 >> 30));

Output:
U31 right shifted: 0x00000001 (expected)
U32 right shifted: 0xffffffff (not expected)

Казалось бы (в отсутствие кого-либо с детальными знаниями), что компилятор C в XCode для Mac OS X v5.0.1 резервирует MSB как бит переноса, который вытягивается вместе с каждой сменой.

Немного досадно, но обратное НЕ верно:

#define ST00 0x00000001
#define ST01 0x00000002

printf ("ST00 left shifted: 0x%08x\n", (ST00 << 30));
printf ("ST01 left shifted: 0x%08x\n", (ST01 << 30));

Output:
ST00 left shifted: 0x40000000
ST01 left shifted: 0x80000000

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

Кто-нибудь может пролить свет на спецификацию реализации C Posix4? Я чувствую, что окончательный ответ может быть там.

Между тем, похоже, что единственным обходным решением является конструкция по следующим направлениям:

#define CARD2UNIVERSE(c) (((c) == 32) ? 0xFFFFFFFF : (U31 >> (31 - (c))))

Это работает — раздражает, но необходимо.

1
По вопросам рекламы [email protected]