Как работает 1 << 64 - 1?
В http://tour.golang.org/#14 они показывают пример, где число 1 сдвинуто на 64 бит. Это, конечно, приведет к переполнению, но затем оно вычитается на 1, и все хорошо. Как половина выражения приводит к сбою, а все выражение в целом работает нормально?
Мысли:
Я бы предположил, что установка без знака на большее число, чем то, что он позволяет, вызывает взрыв. Казалось бы, память распределяется более свободно в правой части выражения, чем слева? Это правда?
Ответы
Ответ 1
Результат выражения - это константа (время компиляции), и поэтому выражение вычисляется во время компиляции. Спецификация языка требует, чтобы
Константные выражения всегда оцениваются точно; промежуточные значения и сами константы могут значительно повысить точность больше, чем поддерживается любым предписанным типом на языке. следующие юридические декларации:
const Huge = 1 << 100 // Huge == 1267650600228229401496703205376 (untyped integer constant)
const Four int8 = Huge >> 98 // Four == 4 (type int8)
https://golang.org/ref/spec#Constant_expressions
Ответ 2
Это потому, что компилятор GO обрабатывает постоянные выражения как числовые константы. В отличие от типов данных, которые должны подчиняться закону диапазона, биты хранения и побочные эффекты, такие как переполнение, числовые константы никогда не теряют точность.
Числовые константы выводятся только с типом данных с ограниченной точностью и диапазоном в тот момент, когда вы назначаете их переменной (которая имеет известный тип и, следовательно, числовой диапазон и определенный способ хранения номера в битах). Вы также можете заставить их выводиться на обычный тип данных, используя их как часть уравнения, содержащего нечетные константные типы.
Confused? Я тоже..
Ниже приведена длинная запись типов данных и как обрабатываются константы: http://www.goinggo.net/2014/04/introduction-to-numeric-constants-in-go.html?m=1
Ответ 3
Фактически 1 << 64 - 1
не всегда приводит к сдвигу влево 64 и минус 1. Оператор -
применяется перед оператором <<
на большинстве языков, по крайней мере, в любом из известных мне (например, С++, Java,...). Поэтому 1 << 64 - 1
<= > 1 << 63
.
Но Go
ведет себя иначе: https://golang.org/ref/spec#Operator_precedence
Оператор -
приходит после оператора <<
.
Результат сдвига влево на 64 бит основан на типе данных. Это точно так же, как добавление 64 из 0 справа, при разрезании любого Бит, расширяющего тип данных с левой стороны. В некоторых языках переполнение может быть допустимым, а в другом - не.
Компиляторы могут также вести себя по-разному на основе интерпретации, когда ваш сдвиг больше или равен фактическому размеру типа данных. Я знаю, что компилятор Java уменьшит фактический сдвиг так часто по размеру типа данных, пока он не станет меньше размера байт данных.
Звучит сложно, но здесь и простой пример для типа данных long
с размером 64 Bit
.
поэтому i << 64
<= > i << 0
<= > i
или i << 65
<= > i << 1
или i << 130
<= > i << 66
<= > i << 2
.
Как сказано, это может различаться разными компиляторами/языками. Никогда не бывает твердого ответа, не ссылаясь на определенный язык.
Для обучения я бы предложил более общий язык, чем Go
, возможно, как-то из семейства C
.
Ответ 4
Я решил попробовать. По причинам, которые являются тонкими, выполнение выражения как постоянного выражения (1 << 64 -1
) или по частям во время выполнения дает тот же ответ. Это связано с двумя различными механизмами. Постоянное выражение полностью оценивается с бесконечной точностью перед назначением переменной. Пошаговое выполнение явно допускает переполнение и переполнение через операции сложения, вычитания и сдвига, и, следовательно, результат тот же.
См. https://golang.org/ref/spec#Integer_overflow для описания того, как целые числа должны переполняться.
Однако, делая это в группах, то есть 1<<64
, а затем -1
вызывает ошибки переполнения!
Вы можете сделать переполнение переменной, хотя и арифметикой, но вы не можете назначить переполнение переменной.
Попробуйте сами. Вставьте код ниже в http://try.golang.org/
Это работает:
// You can edit this code!
// Click here and start typing.
package main
import "fmt"
func main() {
var MaxInt uint64 = 1
MaxInt = MaxInt << 64
MaxInt = MaxInt - 1
fmt.Println("%d",MaxInt)
}
Это не работает:
// You can edit this code!
// Click here and start typing.
package main
import "fmt"
func main() {
var MaxInt uint64 = 1 << 64
MaxInt = MaxInt - 1
fmt.Println("%d",MaxInt)
}