7
votes

We are writing an emulator where we need sign propagating right shift. The emulated system uses 2's complement numbers.

I read that the >> operator on signed integers in C is implementation defined. So I cannot rely on the fact it will result in the correct bit pattern in all platforms.

This means I'll need to use bit manipulation to reproduce the arithmetic right shift, and I would want to avoid unnecessary branching if possible.

EDIT:

In response to a comment:

"The missing bit is that OP needs to define what result is "correct" when the sign bit is set in x with x >> y"

I basically want to reproduce the SAR x86 instruction's behavior. There the negative numbers are represented using 2's complement. The right shift should basically mean division by 2 for negative numbers too.

This means for bit patterns starting with 1. So for 1xxxxxxx, a right shift with should result 11xxxxxx. For bit patterns starting with 0, so 0xxxxxxx right shift should result in 00xxxxxx. So MSB is "sticky". Shifting by more than word length is not defined.

8
What do you expect it to do?Eugene Sh.
if you need portability, what do you expect your right shift to do? what kind of architecture are you emulating?BeyelerStudios
Not speaking of the fact that the representation of negatives is implementation defined as well (well, there are only 3 possibilities, but still..).Eugene Sh.
After the update: Check the MSB, make unsigned shift, set the shifted-in bit to the MSB value. Where is the problem?Eugene Sh.
And, as I understand, you are implementing something using a specific compiler, right? So you know exactly the implementation of the compiler. So use it accordingly.Eugene Sh.

8 Answers

4
votes
int s = -((unsigned) x >> 31);
int sar = (s^x) >> n ^ s;

This requires 5 bitwise operations.

Explanation

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.

3
votes

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.

2
votes

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));
1
votes

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;
}
0
votes

One possible approach is to first perform an unsigned right shift, then sign extend the shifted value based on the value of the most significant bit. Using the fact that when adding two bits a and b, the sum bit is a ^ b and the carry bit is a & b, we can construct sign extension in two ways. As it turns out, using the approach based on the sum bit is more efficient.

The code below shows the emulation of arithmetic right shift as function arithmetic_right_shift() together with a test framework; T is the integer type you wish to operate on.

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define T int
#define EXTEND_USING_CARRY_BIT  (1)
#define EXTEND_USING_SUM_BIT    (2)

#define SIGN_EXTEND_METHOD EXTEND_USING_SUM_BIT

T arithmetic_right_shift (T a, int s)
{
    unsigned T mask_msb = (unsigned T)1 << (sizeof(T) * CHAR_BIT - 1);
    unsigned T ua = a;
    ua = ua >> s;
    mask_msb = mask_msb >> s;
#if (SIGN_EXTEND_METHOD == EXTEND_USING_SUM_BIT) 
    return (T)((ua ^ mask_msb) - mask_msb);
#else // SIGN_EXTEND_METHOD
    return (T)(ua - 2 * (ua & mask_msb));
#endif // SIGN_EXTEND_METHOD
}

int sar_ref (int a, int s)
{
    int res;
    __asm mov eax, dword ptr [a];
    __asm mov ecx, s;
    __asm sar eax, cl;
    __asm mov dword ptr [res], eax;
    return res;
}

int main (void) 
{
    unsigned int x;
    int a, s, res, ref;

    s = 0;
    do {
        x = 0;
        do {
            a = (int)x;
            res = arithmetic_right_shift (a, s);
            ref = sar_ref (a, s);
            if (ref != res) {
                printf ("!!!! a=%08x s=%d  res=%08x  ref=%08x\n", 
                        a, s, res, ref);
                return EXIT_FAILURE;
            }
            x++;
        } while (x);
        s++;
    } while (s < 32);
    return EXIT_SUCCESS;
}
0
votes

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
-1
votes

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.

-3
votes

This function will work no matter the machine definition of 'int', by shifting the absolute value (i.e. without the sign) and then adding the sign:

int shift(int value, int count)
{
  return ((value > 0) - (value < 0)) * (abs(value) >> count);
}