3

Why does this print ffffffff?

#define MYMACRO(n) (((uint16_t)0 - 1) >> (16 - (n)))
std::cout << std::hex << MYMACRO(1);

While this prints 1?

#define MYMACRO(n) (((uint32_t)0 - 1) >> (32 - (n)))
std::cout << std::hex << MYMACRO(1);

I tried it with GCC and cpp.sh.

Silicomancer
  • 8,604
  • 10
  • 63
  • 130

2 Answers2

3

An explanation is in the type-promotion-in-c.

(§6.3.1.1 Boolean, characters, and integers):

If an int can represent all values of the original type, the value is converted to an int; ... These are called the integer promotions. All other types are unchanged by the integer promotions.

So for your first case ((uint16_t)0 - 1) is converted to an int for the substraction's needs. So then the right shift operation is an arithmetic right shift instead of a logical right shift.

Community
  • 1
  • 1
Franck
  • 1,635
  • 1
  • 8
  • 11
  • Is there a way to do this calculation in uint16_t while getting the expected 1? – Silicomancer Oct 28 '16 at 22:38
  • Yes: cast the `1` to `uint16_t` as well. – user268396 Oct 28 '16 at 22:43
  • @user268396: Will make no difference. – AnT stands with Russia Oct 28 '16 at 22:45
  • 4
    @Silicomancer: It is not possible to do the *calculation* in `uint16_t`. Regardless of what you do, the calculation itself will be done in `int`. What you can do is force the result back into `uint16_t` *after* the subtraction. Even after that it will promote back to `int`, but at least it won't drop into the negative area. E.g. use `((uint16_t) (0 - 1) >> (16 - (n)))`, which is the same as `((uint16_t) -1 >> (16 - (n)))`. Or, even better, `((uint16_t) ((uint16_t) -1 >> (16 - (n))))` – AnT stands with Russia Oct 28 '16 at 22:46
  • @AnT: Seems you are right. Even the following does not work: #define MYMACRO(n) ((((uint16_t)0) - ((uint16_t)1)) >> (((uint16_t)16) - ((uint16_t)n))) I don't understand exactly why. – Silicomancer Oct 28 '16 at 22:47
  • 1
    @AnT it is possible to cast to `unsigned int` instead of letting the compiler promote to `int` to get the desired behavior. – Mark Ransom Oct 28 '16 at 23:22
  • @Mark Ransom: Yes, tat will work too. By my "not possible" I meant specifically "not possible to force the evaluation to stay in the domain of 16-bit type". – AnT stands with Russia Oct 28 '16 at 23:29
1

Integral Promotions happen in C++. Nonetheless, testing the expressions practically, isn't a bad idea to understand whats happening under the hood...

#include <typeinfo>
#include <iostream>
#include <boost/type_index.hpp>
using namespace std;

int main(){
#define MYMACRO(n) (((uint16_t)0 - 1) >> (16 - (n)))
    std::cout << std::hex << MYMACRO(1);

    std::cout << "\n-----\n";
#undef MYMACRO
#define MYMACRO(n) (((uint32_t)0 - 1) >> (32 - (n)))
    std::cout << std::hex << MYMACRO(1);

    std::cout << "\n--++++--\n";
    std::cout << boost::typeindex::type_id<decltype((uint16_t)0)>().pretty_name() << std::endl;
    std::cout << boost::typeindex::type_id<decltype((uint16_t)0 - 1)>().pretty_name() << std::endl;    
    std::cout << boost::typeindex::type_id<decltype((uint32_t)0 - 1)>().pretty_name() << std::endl;
}

The integer type of the expression after the shift operator isn't relevant in our case. From the output of the above program as seen Live On Coliru

ffffffff
-----
1
--++++--
unsigned short
int
unsigned int

Summarizes that:

  • The sub-expression: (uint16_t)0 yields an unsigned short as usual on most platforms

  • The expression: (uint16_t)0 - 1 yields a type of int; because of integer promotion rules. 1 is an integral constant of type int

  • The expression: (uint32_t)0 - 1) yields a type of unsigned int; still because of usual arithmetic conversions. an unsigned int is considered larger than an int

WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
  • Is there a way to do this calculation in uint16_t while getting the expected 1? – Silicomancer Oct 28 '16 at 22:44
  • @Silicomancer, Yes, you can.... Cast the whole expression to be of type `uint16_t` .... By defining your macro like this: `#define MYMACRO(n) ( (uint16_t)((uint16_t)((uint16_t)0 - 1) >> (16 - (n))))` – WhiZTiM Oct 28 '16 at 23:23
  • @M.M By saying "the resulting type of a C++ expression can be different from one *sub-expression* to its enclosing *expression*" - I took it from the view of treating *sub-expressions* in isolation. For example, the [tenary `?:` operator](http://eel.is/c++draft/expr.cond) has some funny rules in there too... Is it wrong to treat sub-expressions in isolation? I've removed that statement nonetheless. – WhiZTiM Oct 28 '16 at 23:41
  • @M.M, Thanks. Corrected the final bullet point [*usual arithmetic conversions*](http://eel.is/c++draft/expr#11)... I am learning too. :-). Can you please respond to my question in the previous comment? Thanks – WhiZTiM Oct 28 '16 at 23:45
  • @WhiZTiM one of the good things about expressions in C and C++ is that they're independent of context - an expression has a type that doesn't change depending on where it's used – M.M Oct 29 '16 at 00:09
  • @WhiZTiM: These conversion rules exist to allow compilers to provide operator implementations for a small subset of possible types (or type combinations) only, is this correct? – Silicomancer Oct 29 '16 at 08:55