7

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.

Calmarius
  • 18,570
  • 18
  • 110
  • 157
  • What do you *expect* it to do? – Eugene Sh. Aug 07 '15 at 14:23
  • if you need portability, what do you expect your right shift to do? what kind of architecture are you emulating? – BeyelerStudios Aug 07 '15 at 14:24
  • 2
    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. Aug 07 '15 at 14:28
  • 2
    *After the update*: Check the MSB, make unsigned shift, set the shifted-in bit to the MSB value. Where is the problem? – Eugene Sh. Aug 07 '15 at 14:32
  • 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. Aug 07 '15 at 14:34
  • @EugeneSh. I think there exist some clever bit hack no one thinks of... So far I'm doing the check MSB thing, but that extra branch bothers me... – Calmarius Aug 07 '15 at 14:34
  • It can be done without any branching. – Eugene Sh. Aug 07 '15 at 14:35
  • If you want to right shift number `a` by `x`, then you can use `a/pow(2,x)`. But it would be a arithmetic right shift. – Rakholiya Jenish Aug 07 '15 at 14:36
  • A branch isn't *that* bad, the options offered here with division are probably worse, but it depends on the target architecture. For example on Core2, a branch misprediction is about 13 cycles (it could be predicted correctly sometimes) but a division at least 18 and usually more. – harold Aug 07 '15 at 14:50
  • The missing bit is that OP needs to define what result is "correct" when the sign bit is set in `x` with `x >> y`. – chux - Reinstate Monica Aug 07 '15 at 15:34
  • The maximum shift count can also get you into trouble if the simulated machine allows more bits in the shift count than there are bits in the register being shifted. Once you have excess bits in the shift count C shifts become implementation specific. – Gilbert Apr 16 '19 at 11:25
  • The answers on https://stackoverflow.com/questions/53746160/how-to-implement-arithmetic-right-shift-in-c generate better assembly on platforms that *do* provide SAR (which is most of them). Remember, choice of SAR/SLR is IB not UB so you can/shift test it and using it if available (two's complement is required by C these days). – o11c Jun 30 '22 at 00:00

9 Answers9

5
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

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.

Arkku
  • 41,011
  • 10
  • 62
  • 84
2

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));
Community
  • 1
  • 1
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
2

Here is a single-instruction solution for gcc and clang, at -O1 and higher:

x < 0 ? ~(~x >> y) : x >> y

I tested this with clang 8.01 and gcc 8.1, and they both reduced the code to a single sar instruction. Demo is here.

The code splits the problem into two subproblem right-shifts, each involving only non-negative values. The compilers optimize the ~(~x >> y) subexpression to an arithmetic shift right. Then the compiler sees identical code for the "then" and "else" parts of the branch, and optimizes away the branch.

Arch D. Robison
  • 3,829
  • 2
  • 16
  • 26
1

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;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
0

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;
}
njuffa
  • 23,970
  • 4
  • 78
  • 130
0

A cast to a larger integer type will sign extend the bits. This way, we can force the new bits to be the sign extended bits instead of implementation defined bits, by casting to a larger type, performing the shift, then truncating and casting back.

_Static_assert(sizeof(long)>sizeof(int), "sizeof(long) must be > sizeof(int)");
int SHR(int N, unsigned char I) {
    return (int)((long)N>>I);
}

This is a relatively simple and intuitive way that does not require too many bitwise operations, but is only an option if a larger integer type is available.

user16217248
  • 3,119
  • 19
  • 19
  • 37
-1

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.

Rakholiya Jenish
  • 3,165
  • 18
  • 28
  • 4
    `pow` is actually float and pretty costly for such a simple task. It might very well also not be optimized away by the compiler for a constant x as a left shift would. – too honest for this site Aug 07 '15 at 15:29
  • A right shift of a signed two's complement number is dividing by a power of two, and then rounding towards negative infinity. Using the floating-point unit to do this requires setting the rounding mode to "round down". Also, this works only if the floating-point mantissa has enough precision to exactly represent the values. E.g., IEEE FP64 (the usual implementation of `double`0 won't work for shifting int64_t, since FP64 has only 53 bits of precision. – Arch D. Robison Nov 24 '22 at 15:52
-3

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);
}
JL. Sanchez
  • 371
  • 1
  • 7