Операторы Go << и >>

124

Может кто-нибудь объяснить мне использование <<и >>в Go? Я думаю, это похоже на некоторые другие языки.

brianoh
источник

Ответы:

169

Супер (возможно, сверх) упрощенное определение - это просто то, что <<используется для «умножения на 2» и >>для «деленного на 2» - и число после него - сколько раз.

Так n << xи «n раз 2, x раз». И y >> zэто «y делится на 2, z раз».

Например, 1 << 5«1 умножить на 2,5 раза» или 32. И 32 >> 5это «32, разделенное на 2, 5 раза» или 1.

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

Питер Орам
источник
7
Это отличный ответ. Это действительно укрепило мою голову, спасибо.
Сэм Ороско
2
Так должны быть ответы.
Утсав Гупта
103

Из спецификации на http://golang.org/doc/go_spec.html кажется, что, по крайней мере, с целыми числами, это двоичный сдвиг. например, двоичный 0b00001000 >> 1 будет 0b00000100, а 0b00001000 << 1 будет 0b00010000.


Go явно не принимает нотацию 0b для двоичных целых чисел. Я просто использовал это для примера. В десятичном формате 8 >> 1 равно 4, а 8 << 1 равно 16. Сдвиг влево на единицу аналогичен умножению на 2, а сдвиг вправо на единицу - то же самое, что деление на два с отбрасыванием любого остатка.

jcomeau_ictx
источник
4
Отличный ответ. Это имеет большой смысл, когда я думаю, что видел это в коде, имеющем дело со степенями двойки (1 << power = 2 ^ power)
Стивен Смит
6
Я думаю, это будет полное уравнение: (x << n == x * 2 ^ n) (x >> n == x * 2 ^ (- n))
MondayPaper
хороший ответ, мне сначала двоичный сдвиг казался проблематичным, но преобразование значения в десятичное с степенями в 2 хорошо помогает понять это
Минхаджул
31

Операторы << и >> являются арифметическими операторами Go .

<<   left shift             integer << unsigned integer
>>   right shift            integer >> unsigned integer

Операторы сдвига сдвигают левый операнд на количество сдвигов, указанное правым операндом. Они реализуют арифметические сдвиги, если левый операнд является целым числом со знаком, и логические сдвиги, если это целое число без знака. Счетчик сдвига должен быть целым числом без знака. Нет верхнего предела для количества смен. Сдвиги ведут себя так, как если бы левый операнд сдвигался n раз на 1 для количества сдвигов n. В результате x << 1 то же самое, что x * 2, а x >> 1 то же самое, что x / 2, но усеченное до отрицательной бесконечности.

peterSO
источник
10

Это в основном арифметические операторы, и то же самое на других языках, вот базовый пример PHP, C, Go

ИДТИ

package main

import (
    "fmt"
)

func main() {
    var t , i uint
    t , i = 1 , 1

    for i = 1 ; i < 10 ; i++ {
        fmt.Printf("%d << %d = %d \n", t , i , t<<i)
    }


    fmt.Println()

    t = 512
    for i = 1 ; i < 10 ; i++ {
        fmt.Printf("%d >> %d = %d \n", t , i , t>>i)
    }

}

GO Демо

С

#include <stdio.h>
int main()
{

    int t = 1 ;
    int i = 1 ;

    for(i = 1; i < 10; i++) {
        printf("%d << %d = %d \n", t, i, t << i);
    }

        printf("\n");

    t = 512;

    for(i = 1; i < 10; i++) {
        printf("%d >> %d = %d \n", t, i, t >> i);
    }    

  return 0;
}

C Демо

PHP

$t = $i = 1;

for($i = 1; $i < 10; $i++) {
    printf("%d << %d = %d \n", $t, $i, $t << $i);
}

print PHP_EOL;

$t = 512;

for($i = 1; $i < 10; $i++) {
    printf("%d >> %d = %d \n", $t, $i, $t >> $i);
}

Демо PHP

Все они выведут

1 << 1 = 2 
1 << 2 = 4 
1 << 3 = 8 
1 << 4 = 16 
1 << 5 = 32 
1 << 6 = 64 
1 << 7 = 128 
1 << 8 = 256 
1 << 9 = 512 

512 >> 1 = 256 
512 >> 2 = 128 
512 >> 3 = 64 
512 >> 4 = 32 
512 >> 5 = 16 
512 >> 6 = 8 
512 >> 7 = 4 
512 >> 8 = 2 
512 >> 9 = 1 
ромовая баба
источник
7

Знаки << и >> в Go похожи на сдвиги (то есть деление или умножение на степень двойки) в других языках, но поскольку Go более безопасен, чем C / C ++, он выполняет дополнительную работу, когда количество сдвигов является числом ,

Инструкции сдвига в процессорах x86 учитывают только 5 бит (6 бит на 64-битных процессорах x86) счетчика сдвига. В таких языках, как C / C ++, оператор сдвига преобразуется в одну инструкцию ЦП.

Следующий код Go

x := 10
y := uint(1025)  // A big shift count
println(x >> y)
println(x << y)

печать

0
0

в то время как программа C / C ++ будет печатать

5
20

источник
3
Для операторов сдвига C и C ++: «Поведение не определено, если правый операнд отрицательный, или больше или равен длине в битах продвинутого левого операнда». Стандарты C и C ++ не гарантируют, что программы C и C ++ будут печатать 5 и 20.
peterSO
@peterSO: Да, вы правы. Я считаю, что каждый стандарт языка программирования должен иметь конкретную реализацию на конкретном процессоре. В контексте одного семейства ЦП (x86-32) поведение всех компиляторов C / C ++ (можно ожидать) одинаково. Причина этого в том, что испускание ровно одной инструкции SHL / SHR / etc для реализации оператора сдвига - лучшее, что может сделать оптимизирующий компилятор C / C ++, когда контекст ничего не сообщает ему о «x» и «y». И, если компилятор точно знает, что код имеет неопределенное поведение, он должен проинформировать об этом пользователя.
2
Я не согласен. Вам следует писать переносимый код. И Linux, и Windows работают на ARM. Сосредоточение внимания на одном семействе процессоров недальновидно. Кроме того, y - это переменная. Фактически, компилятор не знает фактических значений времени выполнения.
peterSO
@Atom Помимо языка, который не дает абсолютно никаких гарантий относительно того, что произойдет, неопределенное поведение, вероятно, будет отличаться даже на одной машине с одним компилятором, если, например, вы измените параметры компиляции (например, оптимизированная сборка). ИМО, полагаться на него каким-либо образом опасно неправильно.
Пол Ханкин
@ Анонимный Да, но это только теория. Можете ли вы привести конкретный пример, где изменение параметров компиляции приводит к разному поведению C / C ++ <<или >>в нем?
6

<<левый сдвиг. >>представляет собой сдвиг вправо с расширением знака, когда левый операнд является целым числом со знаком, и сдвиг вправо с расширением нуля, когда левый операнд является целым числом без знака.

Чтобы лучше понять, >>подумайте о

var u uint32 = 0x80000000;
var i int32 = -2;

u >> 1;  // Is 0x40000000 similar to >>> in Java
i >> 1;  // Is -1 similar to >> in Java

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

Майк Сэмюэл
источник
3

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

В двоичном , 2 имеют тот же эффект. Итак, мы добавляем ноль в конец или удаляем последнюю цифру

Роберт Кинг
источник