1
votes

I am trying to achieve logical right shift in Arduino(i.e. avoid sign extension), and after reading the Arduino BitShift guide (https://www.arduino.cc/en/Reference/Bitshift), it suggests that shifting unsigned variables to the right, wont cause sign extension:

When you shift x right by y bits (x >> y), and the highest bit in x is a 1, the behavior depends on the exact data type of x. If x is of type int, the highest bit is the sign bit, determining whether x is negative or not, as we have discussed above. In that case, the sign bit is copied into lower bits, for esoteric historical reasons:

int x = -16;     // binary: 1111111111110000
int y = x >> 3;  // binary: 1111111111111110 This behavior, called sign extension, is often not the behavior you want. Instead, you may

wish zeros to be shifted in from the left. It turns out that the right shift rules are different for unsigned int expressions, so you can use a typecast to suppress ones being copied from the left.

In my tests, it doesn't work like that:

Serial.print( ((uint32_t)(1<<15)) >> 15, BIN);

Prints:

11111111111111111

Which means, there's sign extension going on. I've also tried the suggested example from there, with the same results.

Am I doing something wrong? Also Is it possible to do a shift and force the operation to be logical instead of arithmetic?

1
You can do away with the cast if you use unsigned integers to begin with, as in (1u << 15) >> 15.Some programmer dude
>> over negative values is implementation-defined behavior.Asu
@Someprogrammerdude even if I do that, sign extension still happens, 15 is not negative though...George TG

1 Answers

4
votes

First, I think you left out some key information: it looks like you must be using an Arduino board with a 16-bit int type (e.g., Arduino Uno).

The problem here is how integer promotion works in C and C++. When you cast a 16-bit signed integer literal, 1<<15, to a 32-bit unsigned integer literal, it will take the following steps:

  1. You're casting from 16-bit up to 32-bit, so it will first extend your existing literal to 32-bits. Since it's a signed literal, it's first sign-extended to a 32-bit signed value.
  2. Now that the operand have the same bit-width as the desired type, the compiler casts it to a 32-bit unsigned integer type.

I don't have a 16-bit machine sitting around to test this on, but I can duplicate the same behavior on my 64-bit laptop with this test program:

#include <stdio.h>
#include <inttypes.h>

int main(int argc, const char* argv[]) {
    printf("%" PRIx64 "\n", ((uint64_t)(1 << 31)));
    // prints ffffffff80000000
    printf("%" PRIx64 "\n", ((uint64_t)(1 << 31)) >> 31);
    // prints 1ffffffff
    return 0;
}

So you can see in this program, it's not the >> operation that's doing the unwanted sign extension, but rather the cast from a 32-bit signed integer to a 64-bit unsigned integer, because the cast actually acts like this:

int32_tint64_tuint64_t

If you want to avoid the extra sign extensions, you should either start with an unsigned literal (as Some programmer dude suggested in his comment), or make a cast to the same-width unsigned type. Any of these should work:

Serial.print( ((uint32_t)(1u<<15)) >> 15, BIN);
Serial.print( ((uint16_t)(1<<15)) >> 15, BIN);
Serial.print( ((uint32_t)(uint16_t)(1<<15)) >> 15, BIN);