The "arithmetic shift right" operation is similar to a normal (logical) shift right, except the most significant (i.e. shifted-in) bits are filled with the sign bit rather than 0. Unfortunately, in C++ (prior to C++20, and also in C), the result of performing a right shift on a signed integer is [compiler/platform] implementation-defined.
Is there a way to perform an "arithmetic shift right" that is guaranteed to provide a correct result regardless of implementation details? Ideally the code is simple enough to inline, and does not contain any conditionals or branches.
Here is a C++ inline function that performs an "arithmetic shift right" on a signed 32-bit integer, regardless of implementation details and with no conditionals or branches. It can be easily adapted to C if needed.
Explanation:
The function name
sarstands for "shift arithmetic right", and is reminiscent of common assembly mnemonics. The function accepts a signed 32-bit integervalas the value to shift, and an unsigned integershas the number of bits to shift right. Note: On some platforms, shifting right by a number of bits equal to or larger than the bit-width of the value being shifted can result in undefined behavior! You can limit the maximum value ofsh(to 31, in this case) to avoid this possibility.Since the result of a right shift on a signed integer is implementation-defined, all of our operations will be done using unsigned numbers. We begin by casting our input value to an unsigned integer
uval.Next, we perform the right shift. Since this is an unsigned shift, the most significant (i.e. shifted-in) bits are filled with 0. However, for a proper arithmetic shift right, we would want them filled with the sign bit, which is the most-significant bit of the original value.
The expression
-((uval & 0x80000000) >> sh)provides the string of high-order sign bits that we need. First, we use bitwise AND (&) with a mask to extract the most significant bit, which is the sign bit. Then, we shift this bit to the rightshplaces. Next, we negate the result, which, on unsigned integers, performs a 2's complement operation. This gives us a number with all higher-order bits also set equal to the [shifted] sign bit! Finally, we perform a bitwise OR (|) to combine these sign bits with our shifteduval, filling the high-order bits with the sign bit.In C++11 or later, we can use the following template to handle any signed integer type:
The explanation of the calculation of
high_bitfrom the template typeTis left as exercise for the reader.In C++20 and later, the right bit-shift operator
>>is guaranteed to be arithmetic shift right for signed integers. For earlier language versions, there are of course are a variety of library and other solutions to this problem, but this answer, based on pure C++ code, is intended to be pedantic.