How can I perform arithmetic right shift in C in a portable way?

旧时模样 提交于 2019-12-01 03:30:54

问题


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.


回答1:


You can do it differently, for example by generating the top and bottom separately and then combining: (not tested)

uint32_t bottom = x >> y;
uint32_t top = -(x >> 31) << (32 - y);
return top | bottom;

The negation there turns the 1 into a full mask of ones, shift back left so they're only where they should be. I'm assuming everything here is an uint32_t, so relying on two's complement negation is fine.

Can't shift by zero. That's a cheap branch though, well predicted. Shifts by zero should dynamically be significantly more rare than shifts by anything else.

If you must avoid it, even that branch can be avoided. For example, (not tested)

uint32_t bottom = x >> y;
uint32_t mask = -((x >> 31) & ~((32 - y) >> 5));
uint32_t top = mask << ((32 - y) & 31);
return top | bottom;

That monstrosity (seriously just use a branch) works by setting the mask to all zeroes if 32 - y has bit 5 set which is equivalent to y being zero under the assumption that y is in 0 .. 31 (you can easily make that true by masking y).

You'd still have to be careful with improper shift amounts, but the handing of that depends on what you're emulating.


Also if you don't mind the branch, I would suggest: (not tested)

if (x & (1u << 31))
    res = ~(~x >> y);
else
    res = x >> y;



回答2:


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.




回答3:


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



回答4:


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.




回答5:


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



回答6:


I think I'd write it like this:

-2>>1==-1 /*statically known; does this platform shift arithmetically?*/
      ? X>>By /*do the implementation-defined thing*/
      : X/((X-X+1)<<By) 
       /*otherwise (unlikely) do division (X-X+1 == appropriately promoted 1) */
      ;


来源:https://stackoverflow.com/questions/31879878/how-can-i-perform-arithmetic-right-shift-in-c-in-a-portable-way

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!