Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is going on with bitwise operators and integer promotion?

I have a simple program. Notice that I use an unsigned fixed-width integer 1 byte in size.

#include <cstdint>
#include <iostream>
#include <limits>

int main()
{
    uint8_t x = 12;
    std::cout << (x << 1) << '\n';
    std::cout << ~x;

    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    std::cin.get();

    return 0;
}

My output is the following.

24
-13

I tested larger numbers and operator << always gives me positive numbers, while operator ~ always gives me negative numbers. I then used sizeof() and found...

When I use the left shift bitwise operator(<<), I receive an unsigned 4 byte integer.

When I use the bitwise not operator(~), I receive a signed 4 byte integer.

It seems that the bitwise not operator(~) does a signed integral promotion like the arithmetic operators do. However, the left shift operator(<<) seems to promote to an unsigned integral.

I feel obligated to know when the compiler is changing something behind my back. If I'm correct in my analysis, do all the bitwise operators promote to a 4 byte integer? And why are some signed and some unsigned? I'm so confused!

Edit: My assumption of always getting positive or always getting negative values was wrong. But from being wrong, I understand what was really happening thanks to the great answers below.

like image 505
Wandering Fool Avatar asked May 27 '15 05:05

Wandering Fool


People also ask

What is the use of bitwise operator in real world?

Examples of uses of bitwise operations include encryption, compression, graphics, communications over ports/sockets, embedded systems programming and finite state machines. A bitwise operator works with the binary representation of a number rather than that number's value.

Are bitwise operations useful?

Data representation. Bitwise operators are a great way to make very efficient use of space when representing data. Imagine the following situation: You're a game developer for a computer game and you want to store players' mouse positions periodically while they play the game.

Which data types are bitwise operators mostly used on?

Bitwise Operators are used for manipulating data at the bit level, also called bit level programming. Bitwise operates on one or more bit patterns or binary numerals at the level of their individual bits. They are used in numerical computations to make the calculation process faster.

Are bitwise operators faster in Python?

Software Engineering Python However, there are other ways to perform the same operations without the use of arithmetic operators. Bitwise operators happen to be much simpler operators, making them quite a bit faster than arithmetic operators.


2 Answers

[expr.unary.op]

The operand of ~ shall have integral or unscoped enumeration type; the result is the one’s complement of its operand. Integral promotions are performed.

[expr.shift]

The shift operators << and >> group left-to-right. [...] The operands shall be of integral or unscoped enumeration type and integral promotions are performed.

What's the integral promotion of uint8_t (which is usually going to be unsigned_char behind the scenes)?

[conv.prom]

A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.

So int, because all of the values of a uint8_t can be represented by int.

What is int(12) << 1 ? int(24).

What is ~int(12) ? int(-13).

like image 150
user657267 Avatar answered Oct 30 '22 02:10

user657267


For performance reasons the C and C++ language consider int to be the "most natural" integer type and instead types that are "smaller" than an int are considered sort of "storage" type.

When you use a storage type in an expression it gets automatically converted to an int or to an unsigned int implicitly. For example:

// Assume a char is 8 bit
unsigned char x = 255;
unsigned char one = 1;

int y = x + one; // result will be 256 (too large for a byte!)
++x;             // x is now 0

what happened is that x and one in the first expression have been implicitly converted to integers, the addition has been computed and the result has been stored back in an integer. In other words the computation has NOT been performed using two unsigned chars.

Likewise if you have a float value in an expression the first thing the compiler will do is promoting it to a double (in other words float is a storage type and double is instead the natural size for floating point numbers). This is the reason for which if you use printf to print floats you don't need to say %lf int the format strings and %f is enough (%lf is needed for scanf however because that function stores a result and a float can be smaller than a double).

C++ complicated the matter quite a bit because when passing parameters to functions you can discriminate between ints and smaller types. Thus it's not ALWAYS true that a conversion is performed in every expression... for example you can have:

void foo(unsigned char x);
void foo(int x);

where

unsigned char x = 255, one = 1;
foo(x);       // Calls foo(unsigned char), no promotion
foo(x + one); // Calls foo(int), promotion of both x and one to int
like image 20
6502 Avatar answered Oct 30 '22 02:10

6502