We are writing an emulator where we need sign propagating right shift. The emulated system uses 2\'s complement numbers.
I read that the >>
operat
To be portable and avoid implementation defined behavior of right shifting of signed integers, do all shifting with unsigned
.
Follows is a variation on @harold answer. It does not shift by the bit width (which is UB) nor depend on 2's complement. No branching. If on a rare machine not using not 2's complement, could create a trap value.
#if INT_MAX == 0x7FFF && UINT_MAX == 0xFFFF
#define W 16
#elif INT_MAX == 0x7FFFFFFF && UINT_MAX == 0xFFFFFFFF
#define W 32
#else
// Following often works
#define W (sizeof (unsigned)*CHAR_BIT)
#endif
int TwosComplementArithmeticRightShift(int x, int shift) {
unsigned ux = (unsigned) x;
unsigned sign_bit = ux >> (W-1);
y = (ux >> shift) | (((0-sign_bit) << 1) << (W-1-shift));
return y;
}
or as a one-liner
y = (((unsigned) x) >> shift) | (((0-(((unsigned) x) >> (W-1))) << 1) << (W-1-shift));
Arithmetic right shift is equivalent to division by 2-to-the-number-you're- shifting-by, rounding towards zero if the divisor was nonnegative or towards infinity otherwise.
Hence a generic portable arithmetic right-shift macro in GNU C could be:
#if /*$auto*{{{*/ \
__GNUC__>=7
#define $let(L,R) __auto_type L = R
#else
#define $let(L,R) __typeof(R) L = R
#endif //}}}
#define $sar(X,By) /*{{{*/ \
(__extension__({ \
$let($sar_X,X); \
$let($sar_divisor, ($sar_X*0+1)<<(By) ); \
$sar__($sar_X, $sar_divisor); \
}))
#define $sar__(X,Divisor) ((X)/(Divisor)+((X)%(Divisor)>=0?0:-1))
/*}}}*/
signedX >> shift
often does the equivalent with less machine code (whether it does is implementation defined), and so you could have:
#if (-2>>1==-1 && -2l>>1==-1 && -2ll>>1==-1)
//does signedX>>Shift do an arithmetic right shift?
#define $sar(X,By) ((X)>>(By))
#else
//...the previous snippet...
#endif
I don't see any major problem in using >>
but still if you want to do arithmetic right shift then you can divide the number by 2
to the power x
, where x
is the amount of right shift you want to do because dividing a number by two is equivalent to a single right shift.
Let's say you want to do a >> x
. Then it can also be achieved by doing a / (int)pow(2,x)
. pow(2,x)
is the mathematical power or you can also take it as 2
to the power x
.
Here is a simple hack that should work for all valid shift values:
// shift x right y bits (0..31) with sign replication */
uint32_t sar32(uint32_t x, uint32_t y) {
uint32_t bottom = x >> y;
uint32_t top = -((x & (1u << 31)) >> y);
return top | bottom;
}
You might want to define the behavior for shift counts greater or equal to the word size:
// shift x right y bits with sign replication, intel behavior */
uint32_t sar32(uint32_t x, uint32_t y) {
uint32_t bottom = x >> (y &= 31);
uint32_t top = -((x & (1u << 31)) >> y);
return top | bottom;
}
If you can have platform-specific code, you could test the existing >>
operator (which may or may not do what you want for signed integers, but quite likely it will extend the sign). This is by far the simplest and most efficient solution for most platforms, so if portability is a concern I would just offer another solution as fallback. (I'm not altogether sure that there is any good way to test for this with the preprocessor, though, so the test would need to go into a build solution.)
If you want to do it manually, you might do it by conditionally bitwise-ORing a mask of high bits, or in many cases:
#define asr(x, shift) ((x) / (1 << (shift)) // do not use as is, see below
The problem with the division solution is that the maximum divisor needed is not representable in the same signed type as x
, so you would need to cast the types appropriately for the type of x
and the necessary shifts (e.g., first to a larger type and then back since the result will fit).
This solution follows from the fact that shifting binary numbers is equivalent (in an arithmetic sense) to multiplying and dividing by powers of two; this applies to both the division to simulate the arithmetic right shift, and the left-shift of 1 to obtain the power of two divisor.
However, it is not exactly equivalent to the sign-extending right shift on two's complement machines, in particular if the division of a negative x
results in zero: the true sign-extending shift should give -1
(all bits 1) on a two's complement machine - this would be -0
on one's complement. Similarly the negative result may be off by one with negative x
, again due to difference between two's and one's complement. I would argue that the division gives the correct arithmetic result, but it does not match sign-extending results, and may thus be unsuitable for an emulator.
int s = -((unsigned) x >> 31);
int sar = (s^x) >> n ^ s;
This requires 5 bitwise operations.
As already mentioned, an arithmetic right shift x >> n
corresponds to the division x / 2**n
. In case the system supports only logical right shift, a negative number can be first converted into a positive number and then its sign is copied back afterwards sgn(x) * (abs(x)/2**n)
. This is equivalent to multiply with +/-1 before and after the right shift sgn(x) * ((sgn(x)*x)/2**n)
.
Multiplying an integer with +/-1 can be emulated with the conditional branchless negation s^(s+x)
or (x^s)-s
. When s
is 0
, nothing happens and x
remains unchanged, so a multiplication with 1. When s
is -1
, we obtain -x
, so a multiplication with -1
.
The first line of the snippet, -((unsigned) x >> 31)
, extracts the sign bit.
Here, the unsigned
conversion ensures compilation into a logical right shift (SHR in assembly). Therefore, the immediate result is 0 or 1, and after the negation s
is 0
or -1
as desired.
With two branchless negations before and after the shift, we arrive at ((s^s+x) >> n) + s ^ s
. This performs a division with rounding the result towards zero (e.g. -5>>1 = -2
). However, an arithmetic right shift (SAR in assembly) floors the result (i.e. -5>>1 = -3
). To achieve this behaviour, one has to drop the +s
operation.
A demo is here: https://godbolt.org/ and https://onlinegdb.com/Hymres0y8.
PS: I arrived here, because gnuplot has only logical shifts.