This question is inspired by my attempts to answer another question: Converting decimal/integer to binary - how and why it works the way it does?
The only documentation for the bitwise shift operators that I can find says:
The operations x shl y and x shr y shift the value of x to the left or right by y bits, which (if x is an unsigned integer) is equivalent to multiplying or dividing x by 2^y; the result is of the same type as x. For example, if N stores the value 01101 (decimal 13), then N shl 1 returns 11010 (decimal 26). Note that the value of y is interpreted modulo the size of the type of x. Thus for example, if x is an integer, x shl 40 is interpreted as x shl 8 because an integer is 32 bits and 40 mod 32 is 8.
Consider this program:
{$APPTYPE CONSOLE}
program BitwiseShift;
var
u8: Byte;
u16: Word;
u32: LongWord;
u64: UInt64;
begin
u8 := $ff;
Writeln((u8 shl 7) shr 7);
// expects: 1 actual: 255
u16 := $ffff;
Writeln((u16 shl 15) shr 15);
// expects: 1 actual: 65535
u32 := $ffffffff;
Writeln((u32 shl 31) shr 31);
// expects: 1 actual: 1
u64 := $ffffffffffffffff;
Writeln((u64 shl 63) shr 63);
// expects: 1 actual: 1
end.
I have run this with both XE3 and XE5, for both 32 and 64 bit Windows compilers, and the outupts are consistent, as commented in the code above.
I expected that (u8 shl 7) shr 7
would be evaluated entirely in the context of an 8 bit type. So when bits are shifted beyond the end of that 8 bit type, those bits are lost.
My question is why the program behaves as it does.
Interestingly I translated the program to C++ and on my 64 bit mingw 4.6.3 obtained the same output.
#include <cstdint>
#include <iostream>
int main()
{
uint8_t u8 = 0xff;
std::cout << ((u8 << 7) >> 7) << std::endl;
uint16_t u16 = 0xffff;
std::cout << ((u16 << 15) >> 15) << std::endl;
uint32_t u32 = 0xffffffff;
std::cout << ((u32 << 31) >> 31) << std::endl;
uint64_t u64 = 0xffffffffffffffff;
std::cout << ((u64 << 63) >> 63) << std::endl;
}
Any byte-sized operand is converted to an intermediate word-sized operand that is compatible with both Smallint and Word before any arithmetic operation is performed.
– Duct*,/,div,mod,and,shl,shr,as
are categorized as multiplying operators. This means the compiler apply the same expression syntax for them. – Duct