Problems with shift left (<<)

Started by
9 comments, last by Alberth 6 years, 1 month ago

Hi guys,

I am having problems with trying to perform a basic 'shift left' on a char.


char temp[1];
temp[0] = buffer[0] << 1;		// buffer[0] is 0xff

After this I have temp[0] writing to a file. Instead of being the expected 0x7F it is written as 0xF8.

Any guidance on what I am doing wrong would be awesome.

Thanks in advance :)

Advertisement

Why would you expect 0x7D? You should get 0xFE. Write a complete program if you want us to understand what's going on.

EDIT:


#include <cstdio>

int main() {
  char buffer[1] = {0xff};
  
  char temp[1];
  temp[0] = buffer[0] << 1;
  
  std::printf("%02x\n", (unsigned char)temp[0]);
}

FF = 11111111

7D = 01111101 why would you expect this?

F8 = 11111000 this can't be the result of (0xFF<<1)

FE = 11111110 this is the result of (0xFF<<1)

This is due to signed integers and 2’s compliment.  According to this answer in Arithmetic bit-shift on a signed integer, bit–shifting with negative numbers is undefined.  Here’s a small demonstration:
 

https://ideone.com/ddPyFy


#include <iostream>
using namespace std;

int main() {
	char a = 0xFF;
	unsigned char b = 0xFF;


	cout
		<< (int)(a << 1) << endl
		<< (int)(b << 1) << endl;
}

-2
510

 

58 minutes ago, alvaro said:

Why would you expect 0x7F? You should get 0xFE. Write a complete program if you want us to understand what's going on.

EDIT:



#include <cstdio>

int main() {
  char buffer[1] = {0xff};
  
  char temp[1];
  temp[0] = buffer[0] << 1;
  
  std::printf("%02x\n", (unsigned char)temp[0]);
}

 

Oh, you are right. Turns out I was thinking backwards. I wanted to shift right.

But by shifting right using the code in my OP, I am still getting FF. So I am not sure what is happening there.

It's the sign extension (see link from fastcall)

Also, MSVC has an compiler option to treat chars as signed or unsigned by default.

I think the proper way to get around is to use unsigned numbers anywhere:

    unsigned char buffer[1] = {0xff};
    unsigned int init = buffer[0];  // 0x000000ff
    unsigned char temp[1];
    temp[0] = buffer[0] << 1;        
    unsigned int ls = temp[0];        // 0x000000fe
    temp[0] = buffer[0] >> 1;        
    unsigned int rs = temp[0];        // 0x0000007f

 

Thanks JoeJ,

Yep, it was the signed / unsigned thing that was tripping me up in the end.

Thanks to everyone for the help :)

Just for fun, the right shift behavior is actually not defined by the specification. It's valid for a compiler to use sign-extension (a.k.a. arithmetic shifting, where the value of the MSB is replicated), and also valid for a compiler to use zero-fill (a.k.a. logical shifting, where the MSB's become 0).

In practice though, every compiler that I've ever used will use sign-extension for signed types (e.g. int32_t) and zero-fill for unsigned types (e.g. uint32_t), and pretty much every game project that I've worked on will contain some code that assumes that these rules are true. So despite the C++ spec failing to define this, it's a defacto standard :)

3 hours ago, Hodgman said:

Just for fun, the right shift behavior is actually not defined by the specification. It's valid for a compiler to use sign-extension (a.k.a. arithmetic shifting, where the value of the MSB is replicated), and also valid for a compiler to use zero-fill (a.k.a. logical shifting, where the MSB's become 0).

In practice though, every compiler that I've ever used will use sign-extension for signed types (e.g. int32_t) and zero-fill for unsigned types (e.g. uint32_t), and pretty much every game project that I've worked on will contain some code that assumes that these rules are true. So despite the C++ spec failing to define this, it's a defacto standard :)

If memory serves me right, the "zero-fill for unsigned types" rule is actually in the standard. It's only for signed types that you might in principle run into trouble.

The main thing to remember is "when doing bit manipulation, stick to unsigned types".

 

4 hours ago, Hodgman said:

Just for fun, the right shift behavior is actually not defined by the specification. It's valid for a compiler to use sign-extension (a.k.a. arithmetic shifting, where the value of the MSB is replicated), and also valid for a compiler to use zero-fill (a.k.a. logical shifting, where the MSB's become 0).

I checked this, and actually, it's partly defined (from a local copy of cpprreference.com, the site seems down currently):

Quote

For unsigned a and for signed a with nonnegative values, the value of a >> b is the integer part of a/(2**b). For negative a, the value of a >> b is implementation-defined (in most implementations, this performs arithmetic right shift, so that the result remains negative)

Only for MSB==1 in the signed case, the operator is implementation defined. Also as usual it's completely undefined if b is out of range.

This topic is closed to new replies.

Advertisement