Учитывая 32-разрядное целое число со знаком num
, следующее выражение возвращает ноль, если оно отрицательное, или исходное неизмененное значение в противном случае:
(~num >> 31) & num
Эта операция иногда называется зажатие ; значения меньше нуля фиксируются до нуля.
Объяснение
Только положительные целые числа (и ноль) имеют 0
для их знакового бита , который является самым левым или "старшим значащим битом" (a.ka., " MSB * 1025) * "). Давайте рассмотрим 32-битный случай. Поскольку позиции битов нумеруются слева направо, начиная с 0, знаковый бит - «бит 31». Если перевернуть этот бит и затем распространить его на каждую из 31 позиции бита, вы получите результат, в котором:
- для положительных значений и нуля, все биты установлены (
0xFFFFFFFF
, -1
) или
- для отрицательных значений все биты очищаются (
0x00000000
, 0
).
Путем маскирования исходного значения с этим результатом вы обнулили значение, но только если оно изначально было отрицательным.
Примечания
Поскольку &
(поразрядно- AND
) имеет очень низкий приоритет в C # , вам обычно придется заключать эти выражения в внешние скобки:
((~num >> 31) & num)
Если num
равно без знака (например, uint ui
), вы должны использовать приведение, чтобы убедиться, что сдвиг подписан. Это называется вправо-арифметическим сдвигом , и это гарантирует, что MSB дублируется в каждую сдвинутую вправо позицию:
((int)~ui >> 31) & ui
Для 64-битных значений сдвиг на 63 бита вместо 31:
/* signed */ long v; (~v >> 63) & v
/* unsigned */ ulong ul; ((long)~ul >> 63) & ul
Как показано, вы должны использовать оператор ~
(поразрядно- NOT
), чтобы перевернуть бит знака. Если вы попытаетесь использовать «унарный минус» -
, вместо этого вы получите неправильный ответ для значения 0x80000000
, поскольку это одно из двух целочисленных значений, на которое не влияет применение знака минус . (Другой - ноль, но в любом случае он работает правильно). С другой стороны, битовое значение - NOT
гарантированно перебрасывает каждый бит любого / каждого значения.
Если вы спешите, вот несколько проверенных методов расширения, готовых для копирования / вставки:
public static int Clamp0(this int v) => v & ~v >> 31;
public static long Clamp0(this long v) => v & ~v >> 63;
Узнайте больше о неразветвленном коде!
Этот пример кода, представленный выше, является одним из самых простых примеров с битами , который демонстрирует код без ветвей . Если вы с ним не знакомы, этот термин обычно относится к широкому спектру методов микрооптимизации, которые пытаются минимизировать условные переходы в пользовательском коде, чтобы уменьшить вероятность ошибочных прогнозов в конвейере ЦП.