0

For uint32_t and uint64_t results are expected, but promotions for uint8_t and uint16_t are strange. Tests on c++14/c++17, gcc and clang, 64-bit linux, sizeof(int) == 4.

#include <cstdint>    
using namespace std;

static_assert( uint8_t(-1)*uint8_t(-1) == 0xfe01);  // OK, but why not 1?
static_assert( uint8_t(-1)*uint8_t(-1) == 1);       // error: static assertion failed
static_assert( uint16_t(-1)*uint16_t(-1) == 1);     // error: static_assert expression is not an integral constant expression
static_assert( uint32_t(-1)*uint32_t(-1) == 1);     // OK
static_assert( uint64_t(-1)*uint64_t(-1) == 1);     // OK

Is in the following case std::uint16_t promoted to int?

static_assert( uint16_t(-1)*uint16_t(-1) == uint16_t(uint16_t(-1)*uint16_t(-1)));

The compiler messages are:

error: static_assert expression is not an integral constant expression
static_assert( uint16_t(-1)*uint16_t(-1) == uint16_t(uint16_t(-1)*uint16_t(-1)));

note: value 4294836225 is outside the range of representable values of type 'int'
static_assert( uint16_t(-1)*uint16_t(-1) == uint16_t(uint16_t(-1)*uint16_t(-1)));

More interestingly the same assert for std::uint8_t is correct, but fails:

static_assert( uint8_t(-1)*uint8_t(-1) == uint8_t(uint8_t(-1)*uint8_t(-1)));  //error: static_assert failed
static_assert( uint8_t(-1)*uint8_t(-1) == 0xfe01);  // does not fail

So it looks like uint8_t is promoted to uint16_t, but uint16_t is promoted to signed int. uint32_t is not promoted uint64_t.

Anyone can tell me why these promotions are done, is it specified in standard, or can differ on different implementations?

user2622016
  • 6,060
  • 3
  • 32
  • 53
  • Converting `-1` to any unsigned integral type gives the maximum value that type can represent. It is not possible to represent the value `-1` in an unsigned type. – Peter Oct 30 '20 at 11:12

3 Answers3

2

it looks like uint8_t is promoted to uint16_t, but uint16_t is promoted to signed int. uint32_t is not promoted uint64_t.

No, that's not correct. Only types narrower than int are promoted to int, so if int has more than 16 bits then both uint8_t and uint16_t will be promoted to int.

In the case of uint8_t(-1)*uint8_t(-1), it's equivalent to 0xFF*0xFF and the result will be 0xFE01

OTOH uint16_t(-1)*uint16_t(-1) is the same as 0xFFFF*0xFFFF which results in 0xFFFE0001 = 4294836225 and is outside int's range. And signed overflow is undefined behavior

phuclv
  • 37,963
  • 15
  • 156
  • 475
  • So why uint8_t(-1)*uint8_t(-1) == 0xfe01, and not 0xfffffe01 (32-bit)? – user2622016 Oct 30 '20 at 11:12
  • 2
    @user2622016 `uint8_t(-1)` is 0xFF, 0xFF*0xFF completely lies inside int's range. There's no overflow happening there. uint8_t(0xFF) is zero extended to int(0xFF) instead of sign extended because it's an unsigned type – phuclv Oct 30 '20 at 11:15
  • Do you know how to suppress the promotion to signed int, and get it promoted to unsigned int? I want to have results of uint16_t multiplication to be unsigned type, so no UB, on every architecture, even if sizeof(int) == 2 or sizeof(int)==8. – user2622016 Oct 30 '20 at 11:31
  • OK, I get it. I need explicit cast to unsigned in between to suppress cast to signed int: uint16_t(unsigned(uint16_t(-1))*unsigned(uint16_t(-1))) == 1 – user2622016 Oct 30 '20 at 11:44
  • 1
    `Do you know how to suppress the promotion to signed int` Use `unsigned`. Not as unsigned types, but as `unsigned(uint16_t(-1))`. Ie. explicitly convert the value to a type that has rank greater or equal to that of an `int`. Something along `static_assert(uint16_t(unsigned(uint16_t(-1))*unsigned(uint16_t(-1))) == 1);` – KamilCuk Oct 30 '20 at 12:01
2

Do unsigned integers get promoted to signed?

It depends on the size of the operand in relation to size of int. But yes, it is typical.

static_assert( uint8_t(-1)*uint8_t(-1) == 0xfe01);  // OK, but why not 1?

It is not 1 because uint8_t(-1) is 255 and 255*255 is 65025. On systems with 32 bit int, that is within representable values.

So it looks like uint8_t is promoted to uint16_t

No. Promotions always happen to int or integer of higher rank. uint8_t could be promoted to a 16 bit type on systems where int is 16 bits, but since all values of uint8_t are representable by signed int, that will be the result of the promotion.

can differ on different implementations?

Range of representable values differ between language implementations, which affects what the promoted type will be.

is it specified in standard

Yes.

Do you know how to suppress the promotion to signed int, and get it promoted to unsigned int?

No, there is no way to change the promotions. But you can cast the value to the desired type explicitly so that there is no promotion.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Thanks! I went with: uint16_t(unsigned(uint16_t(0xFFFF))*unsigned(uint16_t(0x8001))). There is no UB, and result is uint16_t. – user2622016 Oct 30 '20 at 12:39
0

Arithmetic operators do not accept types smaller than int as arguments.
For smaller arguments integral promotion to int is applied.

This is the reason for some inconsistency in results:

  • when multiplying types smaller than int you get promotion,
  • when multiplying larger types you don't (if they are the same).

The exact rules are too complicated to summarize without making an error, but are described in those CppReference articles:

Zbyl
  • 2,130
  • 1
  • 17
  • 26