Search code examples
c#operatorsbit-shift

Why would a 32 bit shift in C# return the value it was originally shifting?


I am just seeking some clarification on the C# bit shift.

When I shift the bits of a UINT32 to the right by 32 I get the value shifted back. My expected result is for the value to be zeroed 00000000

For example:

System.Diagnostics.Debug.WriteLine((Convert.ToUInt32("FFFFFFFF", 16) >> 8).ToString("X8"));
System.Diagnostics.Debug.WriteLine((Convert.ToUInt32("FFFFFFFF", 16) >> 16).ToString("X8"));
System.Diagnostics.Debug.WriteLine((Convert.ToUInt32("FFFFFFFF", 16) >> 24).ToString("X8"));
System.Diagnostics.Debug.WriteLine((Convert.ToUInt32("FFFFFFFF", 16) >> 32).ToString("X8"));

System.Diagnostics.Debug.WriteLine((Convert.ToUInt32("FF", 16) >> 8).ToString("X"));
System.Diagnostics.Debug.WriteLine((Convert.ToUInt32("FFFF", 16) >> 16).ToString("X"));
System.Diagnostics.Debug.WriteLine((Convert.ToUInt32("FFFFFF", 16) >> 24).ToString("X"));
System.Diagnostics.Debug.WriteLine((Convert.ToUInt32("FFFFFFFF", 16) >> 32).ToString("X"));

Produces

00FFFFFF
0000FFFF
000000FF
FFFFFFFF

0
0
0
FFFFFFFF

Can someone please explain why this would be the case?


Solution

  • This is an expected and documented behavior - when count is 32 shift (for int/uint) is actually 0 bit shift as only 5 lower bits are considered (32 & 0x1f is 0):

    Bitwise and shift operators (C# reference): Shift count of the shift operators

    If the type of x is int or uint, the shift count is defined by the low-order five bits of the right-hand operand. That is, the shift count is computed from count & 0x1F (or count & 0b_1_1111).


    Based in the comment your actual question seem to be "why C# designers decided to define it that way instead of some other or better yet sticking to undefined behavior similar to C/C++" which is really opinion based (unless there is some official design document exist that saved particular discussions and decision):

    While UB potentially allows some additional optimizations in resulting code or simplifying compiler it also leads to code that may completely change its behavior when used with a different compiler. In general you may find that C# and .Net favor foolproof correctness over potential performance gains. C/C++ on other hand rely more on developers having solid understanding of language boundaries to produce code that runs the same on all compilers.

    As for why particular behavior was selected - it rarely matter which particular choice to use to define the behavior as different compilers/ processors have different preferred outcomes for any particular undefined behavior.

    Additional consideration is ease of documenting - something like "if 16th bit is 0 shifts by more than 31 (for signed) and 32 (unsigned) produce 01 repeated 16 times, otherwise minus one for signed and zero for unsigned unless count is more than 42, then..."