Search code examples
cbit-manipulationbit-shift

How to bit shift left or right based on sign of int?


I need to bit shift x by b, where b can be positive (shift left), zero (nop), or negative (shift right).

C bit shift doesn't handle negative shifts.

Can I define an inline func or macro to do this? Ideally, I wouldn't need to know the type of x when I write this - the macro (or inline func?) should support any integral type.


Solution

  • As noted in comments 1 and 2 to Barmar's answer:

    You could use an inline function to avoid multiple evaluations of arguments:

    static inline uint32_t SHIFT(uint32_t val, int16_t amt)
    {
        return (amt >= 0) ? val << amt : val >> -amt;
    }
    

    Choose the parameter types to suit your needs — but the shift amount could be as small as int8_t and not run into range problems in the foreseeable future.

    Note that shifting signed types has ramifications. The C11 standard §6.5.7 Bitwise shift operators sets the rules:

    The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

    The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 x 2E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 x 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

    The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.

    SRobertJames said:

    As written, your idea generated lots of warnings from GCC about conversions.

    And I responded:

    As I said, you need to get the types right — and you may well need multiple functions with different types for the return value and shifted value:

    • SHIFT32 as above for uint32_t;
    • SHIFT64 using uint64_t for 64-bit types;
    • maybe SHIFT16 using uint16_t for 16-bit types;
    • maybe (but probably not) SHIFT8 using uint8_t for 8-bit types.

    You can add variants for signed types if need be, but (as I said before), shifting signed types is not a particularly good idea, though you can find plenty of code that does it.

    You could use a macro to write these functions:

    #define GENERATE_SHIFT_FUNCTION(name, type) \
        static inline type name(size value, uint16_t amt) \
        { \
            return (amt >= 0) ? val << amt : val >> -amt; \
        }
    
    GENERATE_SHIFT_FUNCTION(SHIFT64, uint64_t)
    GENERATE_SHIFT_FUNCTION(SHIFT32, uint32_t)
    GENERATE_SHIFT_FUNCTION(SHIFT16, uint16_t)
    

    Note that there's no semicolon at the end of the macro invocations. If one were present, it would introduce an empty declaration at the top level.

    Now you can use these functions just like any other, and the argument to the functions is evaluated once, avoiding concerns raised about multiple evaluations when a macro is used instead of an inline function.

    It would be feasible to have the functions check that the shift amount is in the range -(size-1) .. +(size-1) — aborting if the value is out of range. This might be a 'debug' or 'development' mode option — you can make things more complicated if that suits your needs.