Что означают две левые угловые скобки в C #?

В основном вопросы в заголовке. Я смотрю исходный код MVC 2:

[Flags]
public enum HttpVerbs {
    Get = 1 << 0,
    Post = 1 << 1,
    Put = 1 << 2,
    Delete = 1 << 3,
    Head = 1 << 4
}

и мне просто любопытно, что делают двойные скобки с левым углом <<.


person lancscoder    schedule 22.03.2010    source источник
comment
если вопрос был о >>, то возможные ответы должны были включать общую программную часть C #   -  person CoffeDeveloper    schedule 07.07.2015


Ответы (15)


Это будет оператор побитового сдвига влево.

Для каждого сдвига влево значение эффективно умножается на 2. Так, например, запись value << 3 умножит значение на 8.

На самом деле он перемещает все фактические биты значения на одно место. Итак, если у вас есть значение 12 (десятичное), в двоичном формате это 00001100; сдвиг влево на одно место превратит это в 00011000, или 24.

person Aaronaught    schedule 22.03.2010

Когда ты пишешь

1 << n

Вы сдвигаете битовую комбинацию 000000001 на n раз влево и, таким образом, помещаете n в показатель степени 2:

2^n

So

1 << 10

На самом деле

1024

Для списка из 5 элементов ваш for будет повторяться 32 раза.

person pid    schedule 29.01.2014
comment
Так это то же самое, что for (int i = 0; i < Math.Pow(2, list.Count)); i++)? - person Robert Fricke; 29.01.2014
comment
@RobertFricke Я бы осторожнее использовал < с int и double - person Ilya Ivanov; 29.01.2014
comment
@ Роберт Фрике: Да. Битовый сдвиг ограничен основанием 2 (недостаток), но намного быстрее (преимущество), чем Math.Pow (), который более гибок и может даже иметь основания и показатели с плавающей запятой. Это становится одной инструкцией машинного кода. - person pid; 29.01.2014
comment
@ Илья Иванов for (int i = 0; i < Convert.ToInt32(Math.Pow(2, list.Count))); i++)? - person Robert Fricke; 29.01.2014
comment
@RobertFricke, ты видишь, как вдруг 1 << list.Count просто сияет, если сравнивать с Convert.ToInt32(Math.Pow(2, list.Count)))) - person Ilya Ivanov; 29.01.2014
comment
@Ilya: правда, я бы предпочел беззнаковые типы, но даже тогда побитовые операции относительно редки в высокоуровневом коде C #. - person pid; 29.01.2014
comment
@IlyaIvanov Ха-ха, да. А также то, что ваш работодатель никогда больше не может вас отпустить: ваш код работает молниеносно, и другие разработчики не могут его понять. - person Robert Fricke; 29.01.2014
comment
Мне трудно поверить, что производительность настолько высока, что было бы бессмысленно Math.Pow один раз найти количество циклов. Тогда вам не нужно беспокоиться о том, что разработчики сбивают с толку всякую ерунду. - person Gusdor; 29.01.2014
comment
@Gusdor: для корпоративных приложений я бы не одобрял трудный для чтения код. Лучше платить производительностью за удобочитаемость. Так что здесь я с вами согласен. - person pid; 29.01.2014
comment
Предполагая, что список не изменяется внутри цикла, было бы более эффективно вычислять количество итераций только один раз перед циклом. - person Andris; 29.01.2014
comment
Левый сдвиг и другие побитовые операторы обычно используются во встраиваемых системах, где даже пара циклов важна для производительности. Поразрядные операторы поддерживаются непосредственно процессором и используются в других языках программирования. Вам не нужно добавлять функции, чтобы они работали, экономя место в ситуациях с нехваткой памяти. Да, если вы работаете с приложением на базе .NET на современном компьютере, вы можете просто включить математическую библиотеку и не беспокоиться о битовой математике, но не ожидайте, что вы сделаете то же самое в чем-то вроде инструментальной цепочки AVR-GCC и не повлияете на нее. представление. - person Sisco; 29.01.2014
comment
Как сказал @Andris, этот код, скорее всего, в любом случае неэффективен. Кроме того, почему сдвиг влево должен выполняться быстрее, чем Math.Pow (2, n)? Разве здесь никто не ожидал, что Math.Pow выполнит сдвиг влево в этом случае? - person Shautieh; 29.01.2014
comment
@Shautieh: Даже если Math.Pow выполняет сдвиг влево, он должен проанализировать параметры, чтобы определить, может ли он это сделать, так что в любом случае есть необходимая дополнительная операция. - person Patrick; 29.01.2014
comment
@Shautieh, даже если Math.Pow использует левый сдвиг в случае базы 2, у вас есть минимум: (1) вызов метода, (2) условное выражение для проверки, если база равно 2, (3) условие, гарантирующее, что мощность может быть преобразована в целое число без потери точности, и (4) сдвиг влево. По сравнению с явным сдвигом влево, который представляет собой единственную операцию, Pow будет медленнее. - person Brian S; 29.01.2014
comment
Если list.Count не будет изменяться во время цикла, может более эффективно вывести операцию за пределы цикла в виде присвоения переменной (зависит от того, насколько хорошо оптимизатор делает). В любом случае комментарий, мягко напоминающий читателю, что делает эта необычная операция (редко используемая идиома) (верхняя граница - 2 ^ list.Count), подойдет даже для опытных программистов. - person Phil Perry; 29.01.2014
comment
@Ingo: Не у всех здесь такой же опыт, как у вас. Гарантирую, был один день, когда вы не знали оператора ‹<. Проявляйте базовое уважение к тем, кто все еще учится (без них не было бы SO). - person Plutor; 29.01.2014
comment
Первый раз, когда вы сталкиваетесь со сдвигом влево / вправо, это кажется непонятным и непонятным. Однако довольно скоро вы поймете это великолепие и начнете использовать их повсюду. Вот почему всегда полезно ознакомиться с побитовыми операторами, если вы изучаете любую производную C. Это пригодится. - person Mordred; 29.01.2014
comment
Тот факт, что они используют list.Count, заставляет меня думать, что они слишком оптимизировали не ту часть кода. - person BlackTigerX; 29.01.2014
comment
@BrianS Нет, если оптимизация выполняется во время компиляции. Если производительность не является строго необходимой, и вы знаете, что ваш компилятор не будет выполнять оптимизацию, это может быть случаем преждевременной оптимизации. - person rob; 29.01.2014
comment
Есть ли случай, когда Pow () и ‹< дадут разные ответы из-за округления? - person BlueTrin; 29.01.2014
comment
@Plutor Не зная, что это нормально (для этого нужны спецификации языка). То, что это трудно понять, трудно читать, другие разработчики не могут понять или что-то в этом роде, является признаком нежелания изучать что-либо, кроме +, -, *, /. Либо это, либо полное незнание того, что такое целые числа и как они представлены. Обходной путь Math.pow(n,2) просто гротеск. Например, в Java это повлечет за собой преобразования в двойные и обратно - person Ingo; 29.01.2014
comment
@rob, правда, компилятор умный, а не Pow эффективный. - person Brian S; 29.01.2014
comment
Я не уверен, почему все видят в этом оптимизацию. Для меня это естественная идиома для выражения силы двойки, и я бы никогда не подумал написать ее по-другому. Использование библиотечной функции для выполнения вычислений по степени двойки затрудняет чтение (ужасная префиксная запись, а не value operator value, который гораздо удобнее читать). Серьезно, вы действительно думаете, что Convert.ToInt32(Math.Pow(2,value)) более читабельно, чем 1<<value, теперь вам объяснили значение оператора? - person Jules; 29.01.2014
comment
И разве вы не считаете своей обязанностью понять смысл всех встроенных операций вашего языка? - person Jules; 29.01.2014
comment
@BrianS Я хочу сказать, что теоретическая эффективность << по pow не имеет значения, если вы не знаете, что ваш компилятор не собирается оптимизировать pow. Если вы используете << для воспроизводимого сценария реальной производительности, вам следует задокументировать его, чтобы будущий сопровождающий, который думает, что pow яснее, не заменил его по прихоти. - person rob; 29.01.2014
comment
@rob: Компилятор не может оптимизировать pow(2, c) до 1 << c, если не докажет, что c < CHAR_BIT * sizeof c. - person Ben Voigt; 30.01.2014
comment
@JackAidley Я тоже. Я в том поколении. Мне часто жаль, что у меня не было опыта с такими вещами. Правда в том, что в этом больше нет необходимости, за исключением очень сложных или загадочных случаев (например, жесткий криптоанализ и тестирование на проникновение). - person asteri; 30.01.2014
comment
Я думаю, будет справедливо упомянуть, что операторы сдвига влево / вправо не работают с переменными точности с плавающей запятой, такими как float или double, поэтому для этих переменных нужно преобразовать их в int / long, выполнить сдвиг, а затем вернуть обратно. Данные, скорее всего, будут потеряны в процессе, если с ними не обращаться правильно. - person John Odom; 30.01.2014
comment
Интересно, почему этот вопрос привлекает столько внимания. Это действительно самый тривиальный вопрос, на который я пока ответил ... - person pid; 30.01.2014

Он называется оператором left-shift. Ознакомьтесь с документацией

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

Простой пример, демонстрирующий оператор left-shift:

for (int i = 0; i < 10; i++)
{
    var shiftedValue = 1 << i;
    Console.WriteLine(" 1 << {0} = {1} \t Binary: {2}",i,shiftedValue,Convert.ToString(shiftedValue,2).PadLeft(10,'0'));
}

//Output:

// 1 << 0 = 1      Binary: 0000000001
// 1 << 1 = 2      Binary: 0000000010
// 1 << 2 = 4      Binary: 0000000100
// 1 << 3 = 8      Binary: 0000001000
// 1 << 4 = 16     Binary: 0000010000
// 1 << 5 = 32     Binary: 0000100000
// 1 << 6 = 64     Binary: 0001000000
// 1 << 7 = 128    Binary: 0010000000
// 1 << 8 = 256    Binary: 0100000000
// 1 << 9 = 512    Binary: 1000000000

Перемещение одного бита влево эквивалентно умножению на два. Фактически, перемещение битов происходит быстрее, чем стандартное умножение. Давайте посмотрим на пример, демонстрирующий этот факт:

Допустим, у нас есть два метода:

static void ShiftBits(long number,int count)
{
    long value = number;
    for (int i = 0; i < count; i+=128)
    {
          for (int j = 1; j < 65; j++)
          {
              value = value << j;
          }
          for (int j = 1; j < 65; j++)
          {
               value = value >> j;
          }
    }
}

static void MultipleAndDivide(long number, int count)
{
      long value = number;
      for (int i = 0; i < count; i += 128)
      {
            for (int j = 1; j < 65; j++)
            {
                value = value * (2 * j);
            }
            for (int j = 1; j < 65; j++)
            {
                value = value / (2 * j);
            }
      }
}

И мы хотим протестировать их так:

ShiftBits(1, 10000000);
ShiftBits(1, 100000000);
ShiftBits(1, 1000000000);
...
MultipleAndDivide(1, 10000000);
MultipleAndDivide(1, 100000000);
MultipleAndDivide(1, 1000000000);
...

Вот результаты:

Bit manipulation 10.000.000 times: 58 milliseconds
Bit manipulation 100.000.000 times: 375 milliseconds
Bit manipulation 1.000.000.000 times: 4073 milliseconds

Multiplication and Division 10.000.000 times: 81 milliseconds
Multiplication and Division 100.000.000 times: 824 milliseconds
Multiplication and Division 1.000.000.000 times: 8224 milliseconds
person Community    schedule 29.01.2014
comment
В криптографии мы предпочитаем побитовое вращение побитовым сдвигам. В некоторых местах используются сдвиги, но они не так распространены, как ротации. - person Reid; 29.01.2014
comment
Это довольно обобщенно. Мне это неудобно, если честно. Меня в основном не устраивает тот факт, что вы не упомянули, что он выполняет (value) * 2 ^ n очень быстро. Кроме того, приведенные вами примеры (хотя и правдивы) упускают то, что я чувствую. - person violet_white; 29.01.2014
comment
@ jaked122, хватит? :) - person Selman Genç; 30.01.2014

Это Побитовый сдвиг влево он работает, сдвигая цифры двоичного эквивалента числа на заданные (правые) числа.

so:

temp = 14 << 2

двоичный эквивалент 14 00001110 сдвиг его 2 раза означает нажатие нуля с правой стороны и сдвиг каждой цифры влево, что делает 00111000 равным 56.

visual

В вашем примере:

i < (1 << list.Count)
  • 0000000001 = 1, если list.Count = 0 результат равен 0000000001 = 1
  • 0000000001 = 1, если list.Count = 1 результат равен 0000000010 = 2
  • 0000000001 = 1, если list.Count = 2 результат равен 0000000100 = 4
  • 0000000001 = 1, если list.Count = 3 результат равен 0000001000 = 8

и так далее. Обычно он равен 2 ^ list.Count (2 в степени list.Count)

person Zaheer Ahmed    schedule 29.01.2014

Это оператор левый битовый сдвиг. Он сдвигает битовую комбинацию левого операнда влево на количество двоичных цифр, указанное в правом операнде.

Get = 1 << 0, // 1
Post = 1 << 1, // 2
Put = 1 << 2,  // 4
Delete = 1 << 3, // 8
Head = 1 << 4  // 16

Это семантически эквивалентно lOperand * Math.Pow(2, rOperand)

person Adam Robinson    schedule 22.03.2010
comment
+1 для фактического показа того, что делает левый битовый сдвиг в этом случае. - person auujay; 22.03.2010
comment
Или более конкретно: 00001, 00010, 00100, 01000, 10000 - person zmbush; 22.03.2010
comment
по количеству двоичных цифр, указанных в правом операнде - на самом деле, это не совсем правильно; например, для 32-битной системы учитываются только первые 5 битов, поэтому << 33 идентично << 1. Точно так же в 64-битной математике << 65 идентично << 1. И сдвиг вправо снова более сложен, так как вам нужно учитывать знак, чтобы знать, чем заполнять. - person Marc Gravell; 23.03.2010

Целью цикла, скорее всего, является создание или работа со всеми подмножествами набора элементов в списке. И тело цикла, скорее всего, также имеет хороший бит (хар-хар) побитовых операций, а именно как еще один сдвиг влево и побитовое и. (Так что переписывать его для использования Pow было бы очень глупо, я не могу поверить, что было так много людей, которые на самом деле предлагали это.)

person user2451227    schedule 29.01.2014
comment
+1 за предположение, что это касается подмножеств элементов списка, что кажется единственным разумным мотивом для такого рода действий. Можно добавить, что это очень плохой метод, если есть хоть какой-то шанс, что список будет довольно длинным, а именно длиннее, чем количество бит в int (можно предположить, что со смещением всех битов цикл будет быть выполнено 0 раз, но на самом деле я думаю, что поведение не определено; на самом деле я помню, что сдвиг битов точно на длину слова часто вообще ничего не дает). - person Marc van Leeuwen; 29.01.2014

Это немного сдвигается. В основном это просто перемещение битов влево, добавляя 0 к правой стороне.

public enum HttpVerbs {
    Get = 1 << 0,    // 00000001 -> 00000001 = 1
    Post = 1 << 1,   // 00000001 -> 00000010 = 2
    Put = 1 << 2,    // 00000001 -> 00000100 = 4
    Delete = 1 << 3, // 00000001 -> 00001000 = 8
    Head = 1 << 4    // 00000001 -> 00010000 = 16
}

Дополнительная информация на http://www.blackwasp.co.uk/CSharpShiftOperators.aspx

person Fabian    schedule 22.03.2010

В дополнение к ответу Selman22 несколько примеров:

Я перечислю несколько значений для list.Count и того, каким будет цикл:

list.Count == 0: for (int i = 0; i < 1; i++)
list.Count == 1: for (int i = 0; i < 2; i++)
list.Count == 2: for (int i = 0; i < 4; i++)
list.Count == 3: for (int i = 0; i < 8; i++)

И так далее.

person Thorsten Dittmar    schedule 29.01.2014

«Битовый сдвиг влево». 1 << 0 означает «взять целочисленное значение 1 и сдвинуть его биты влево на нулевые биты». То есть 00000001 остается без изменений. 1 << 1 означает «взять целочисленное значение 1 и сдвинуть его биты влево на одно место». 00000001 становится 00000010.

person Dathan    schedule 22.03.2010
comment
Для вашего первого примера, я думаю, вы имели в виду нулевые биты, но все остальное правильно. - person Adam Robinson; 22.03.2010
comment
@Adam Спасибо, вы абсолютно правы. Я обновил пост. - person Dathan; 22.03.2010

Его (‹<) оператор побитового сдвига влево, он перемещает битовые значения двоичного объекта. Левый операнд определяет значение, которое должно быть сдвинуто, а правый операнд определяет количество позиций, на которые должны быть сдвинуты биты в значении.

В вашем случае, если значение list.count равно 4, цикл будет выполняться до i ‹(1 ‹---------------- 4), который равен 16 (00010000)

00000001 << 4 = 00010000(16)

person gaurav    schedule 29.01.2014

Это подразумевается во многих ответах, но никогда не говорится прямо ...

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

Например,

Десятичное число 5, сдвинутое влево на единицу, равно десятичному числу 10 или десятичному числу 5, удвоенному.

Десятичное число 5, сдвинутое влево на 3, является десятичным числом 40 или десятичным числом 5, удвоенным 3 раза.

person GaTechThomas    schedule 29.01.2014

Выражение (1 << N) использует битовый сдвиг в C #.

В этом случае он используется для быстрого целочисленного вычисления 2 ^ N, где n от 0 до 30.

Хорошим инструментом для молодых разработчиков, которые не понимают, как работают битовые сдвиги, является Windows Calc в режиме программиста, который визуализирует влияние сдвигов на подписанные числа различного размера. Функции Lsh и Rsh приравниваются к << и >> соответственно.

Оценка с использованием Math.Pow внутри условия цикла (в моей системе) примерно в 7 раз медленнее, чем код вопроса для N = 10, зависит ли это от контекста.

Кэширование «счетчика циклов» в отдельной переменной немного ускорит его, так как выражение, включающее длину списка, не нужно будет повторно вычислять на каждой итерации.

person Peter Wishart    schedule 29.01.2014
comment
1 << list.Count похоже, что это, вероятно, инвариант цикла в любом случае, и в этом случае достойный компилятор переместит его из цикла. Учитывая это, некоторым людям кеширование может показаться преждевременной оптимизацией. Для меня это будет зависеть от того, насколько серьезен код. - person hippietrail; 29.01.2014
comment
В зависимости от того, откуда взялся list, компилятору, вероятно, очень сложно доказать, что он инвариант цикла: например, знает ли компилятор, что он не может быть изменен в другом потоке? Или что это не какой-то причудливый подкласс List, который удаляет элементы при обращении к ним, или что-то подобное? - person Jules; 29.01.2014
comment
Что это от -31 до -2 бит? Это кажется особенно странным, потому что это диапазон из 30 чисел, а от 0 до 30 - из 31 числа. (И разве диапазон на самом деле не должен состоять из 32 чисел?) - person Brilliand; 30.01.2014
comment
@Brilli, извини, я несусмысленно, int << X то же самое, что int << (X-32), но а) это не имеет значения и б) отрицательные сдвиги не вычисляются 2^(32-X)! - person Peter Wishart; 30.01.2014

В предыдущих ответах объяснялось, что, но, похоже, никто не догадался, почему. Мне кажется весьма вероятным, что причина этого кода в том, что цикл повторяется по каждой возможной комбинации членов списка - это единственная причина, по которой я могу понять, почему вы захотите повторить итерацию до 2 ^ {list. Считать}. Следовательно, переменная i была бы неправильно названа: вместо индекса (что я обычно интерпретирую как значение 'i') ее биты представляют собой комбинацию элементов из списка, поэтому (например) первый элемент может быть выбран, если нулевой бит i установлен ((i & (1 << 0)) != 0), второй элемент, если установлен бит один ((i & (1 << 1)) != 0) и так далее. 1 << list.Count, следовательно, является первым целым числом, которое не соответствует допустимой комбинации элементов из списка, так как оно указывает на выбор несуществующего list[list.Count].

person Jules    schedule 29.01.2014
comment
Но вопрос не в этом, так что на самом деле это не ответ. - person David Conrad; 29.01.2014
comment
Думаю, это ответ. Потому что он проливает на него другой свет: это не просто 2 ^ list.Count: для особенно удобного способа перечисления выборок из списка он вычисляет (я подозреваю) первое число, которое не соответствует не соответствует действительному выбору. Это оказывается 2 ^ list.Count, но намерение (я достаточно уверен) состоит в том, чтобы перечислить все эти комбинации, поэтому тот факт, что это количество возможных комбинаций, является случайным для реального значения выхода из цикла. условие, которое прекращает подсчет, когда у нас заканчиваются комбинации элементов списка. - person Jules; 29.01.2014
comment
Вопрос в том, что означает (1 << list.Count). Ваш ответ ... на другой вопрос, чем тот, который задал ОП. - person David Conrad; 30.01.2014

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

[Fact] public void Bit_shift_left()
{
    Assert.Equal(Convert.ToInt32("0001", 2), 1 << 0); // 1
    Assert.Equal(Convert.ToInt32("0010", 2), 1 << 1); // 2
    Assert.Equal(Convert.ToInt32("0100", 2), 1 << 2); // 4
    Assert.Equal(Convert.ToInt32("1000", 2), 1 << 3); // 8
}
person Mark    schedule 22.03.2010

<< - оператор сдвига левого бита. Если у вас есть число 3, которое равно 00000011 в двоичном формате, вы можете написать 3 << 2, что равно 00001100, или 12 в десятичном формате.

person Vavřinec Kubíček    schedule 02.11.2020