Shift

A shift moves every bit of an integer left or right by a number of places. Bits that move past the end are dropped, and new bits come in at the other end.

OperatorOperationCompound formLevel
<<left shift: zeros come in at the bottom<<=12
>>right shift: arithmetic on signed, logical on unsigned>>=12
>>>logical right shift: zeros come in at the top>>>=12
let value: uint8 = 0b00010100;
PrintLine("{} {}", value << 2, value >> 2);

This prints 80 5.

Operand types

The left operand is an integer (or a character), and the result has its type: a shift never changes the width of the value being shifted. The right operand, the count, is any integer type. It keeps its own type and is not converted to the left operand's:

let one: uint32 = 1;
let count: uint8 = 5;
PrintLine("{}", one << count);

This prints 32, a uint32. A floating-point operand on either side is refused, with error: operator '<<' requires an integer or character left operand, but found 'float64' or error: operator '<<' requires an integer right operand, but found 'float64'.

A left shift keeps only the bits that still fit. A uint8 holding 200 shifted left by one is 144, not 400: the top bit fell off. Shift a wider type when the result needs the room.

Right shifts

Shifting right empties the top bits, and there are two ways to fill them:

Left operand>> fills with>>> fills with
signedcopies of the sign bit (arithmetic)zeros (logical)
unsignedzeros (logical)— not allowed
let negative: int8 = -8;
PrintLine("{} {}", negative >> 2, negative >>> 2);

This prints -2 62. The arithmetic shift keeps the sign and divides by four, rounding towards minus infinity — so -7 >> 1 is -4, where -7 / 2 is -3. The logical shift treats the value as plain bits.

An unsigned >> is already logical, so >>> exists only for signed operands:

error: operator '>>>' requires a signed integer left operand, but found 'uint8'

The shift count

A count must be at least zero and less than the bit width of the left operand: 0 to 31 for an int32, 0 to 7 for a uint8. Shifting by the full width or more does not produce a meaningful value; to clear a value, assign zero.

Counts at or past the width.
rux 0.4.0 does not yet check this. A shift by the width or more compiles, and what it produces depends on the operand's width, so keep every count below the width yourself.

Precedence

Shifts bind more loosely than + and - and more tightly than the comparisons and the bitwise operators:

PrintLine("{}", 1 << 2 + 1);

This is 1 << (2 + 1), which prints 8. Parenthesise a shift that builds a mask, as in flags & (1 << n), even where precedence would already group it that way.

Compound forms

<<=, >>= and >>>= update a mutable place. Like >>>, >>>= needs a signed target:

var bits: int32 = -16;
bits >>>= 1;
bits <<= 2;

Packing fields

Shifts and masks together store several small fields in one integer. Each field is shifted into place and combined with |, and read back by shifting it down and masking off the rest:

let red: uint32 = 0xFF;
let green: uint32 = 0x80;
let blue: uint32 = 0x20;
let color = (red << 16) | (green << 8) | blue;
let g = (color >> 8) & 0xFF;

See also