Does uint16_t get promoted to int? And is it safe to typecast uint16_t to uint32_t?
Asked Answered
P

3

7
uint32_t a = 10;
uint16_t b = 0;

if (a > b)
{
   printf("a > b\n");
}

I was expecting that b would get promoted to int and the compiler would complain about comparing signed and unsigned. But the compiler did not raise any warning.

When I change if (a > b) to if (a > (b * 10)) the compiler says

warning: comparison between signed and unsigned

Why is this so?

And is it safe to typecast this to uint32_t?

if (a > (uint32_t)(b * 10))
Pumping answered 13/3, 2023 at 16:6 Comment(17)
b * 10 is promoted to signed int, because 10 is signed int.Malayalam
You could use 10u to keep it unsigned.Zohara
I'm guessing the compiler is smart enough to realize that promoting b to int can't possibly result in a negative value, and suppress the spurious warning.Lightfooted
@EugeneSh. Thanks. Does uint16_t gets promoted to int in first case? Also is it safe to typecast uint16_t to uint32_t?Pumping
I didn't notice that this was tagged C and C++ both. It doesn't matter in this case, but as per tag usage policies, it should have been tagged C since there's nothing here that's C++ specific (save for operator overloading of > maybe).Interfluent
@EugeneSh. I think it's a little more complicated than that, at least for C. In C, the "integer promotions" are first performed on b. If an int can represent all values of b's type, then it is promoted to an int. But on a machine with 16-bit integers, that would not be the case. So in the first case, the multiplication would be applied to two signed integers. But in the latter, it would be an unsigned integer and a signed integer, so it would be unsigned. At least, that's how I read the standard.Bontebok
@EugeneSh. Well, it is promoted since the binary * operator says that integer promotions are performed at the operands. And then since both operands happen to have type int, no further conversions are necessary. In case it had said b * (unsigned short)10, b would still have been promoted to int.Interfluent
@Interfluent Isn't is a subject of "usual arithmetic conversions"? "Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank."Malayalam
@EugeneSh. Did you read my comment? The "integer promotions" happen before both operands are examined together. It's a two-stage process. First the promotions occur in isolation, and then the two types are examined together. On machines with larger-than-16-bit integers, by the time both operands are considered together, they're already both signed. But that would not be the case on a machine with 16-bit integers.Bontebok
@EugeneSh. And what did it say in the text above that one you quote? Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands: In which case If both operands have the same type, then no further conversion is needed. applies.Interfluent
@Interfluent On a machine with 16-bit integers, I believe both operands would be converted to unsigned.Bontebok
@TomKarzes Or rather, on such a machine then uint16_t corresponds to unsigned int so it won't get converted, but the 10 would get converted to uint16_t.Interfluent
@Interfluent Exactly, b would be left unchanged and the 10 would become unsigned. So in that case the result would be unsigned. I mentioned that in my earlier comment.Bontebok
@Lundin, in C++ usual arithmetic promotion, integer promotion attemps to keep signedness. Otherwise if unsigned operand has higher rank, that would be the selected type. Signed promotion is somewhat the last resort.Dorie
@Dorie No. C++ works just like C here, both are similarly dysfunctional. They do not attempt to keep signedness at all, they promote all small integer types to (signed) int regardless of previous signedness. And from there, if two operands have the same conversion rank, it picks unsigned over signed.Interfluent
@Interfluent I don't know which version of C++ you are dealing with. You can find the spec here: en.cppreference.com/w/cpp/language/usual_arithmetic_conversions specifically note C++23 specs for forward compatibility.Dorie
@Dorie It's more or less the same for every C and C++ version. And I just explained what it says to you. Nowhere does it attempt to "keep signedness", which is why the rules are dysfunctional.Interfluent
I
6

Does uint16_t gets promoted to int?

If int is 32 bits, sure. If not, then it isn't promoted.

I was expecting that b would get promoted to int

It is, on a 32 bit system.

But the compiler did not raise any warning.

It's not really the compilers job to keep track of that. If you get a warning, it's just a bonus. But also please note that in your example the conversion is done in two internal steps: first the integer promotions is performed on the uint16_t making it int. But then immediately afterwards, the usual arithmetic conversions convert that int to a uint32_t. So essentially as far as the programmer is concerned, the uint16_t is converted to uint32_t and that conversion is always safe.

Generally, operations mixing unsigned operands of different sizes are safe.

if (a > (uint32_t)(b * 10))

b gets promoted to int and 10 is already int. There's no way for the multiplication to overflow since it can't get larger than 655350. You can cast to uint32_t afterwards if you like, but it doesn't rally achieve anything except being explicit.

More rugged code would have done the cast before the multiplication or perhaps used 10u instead. But in this specific case it really don't matter.

Interfluent answered 13/3, 2023 at 16:14 Comment(2)
Might want to mention that (uint32_t)b * 10 is needed on systems with 16-bit ints?Desecrate
At the risk of being overly pedantic, the usual arithmetic conversions convert that int to unsigned int. Yes, uint32_t on a 32-bit system is a synonym for unsigned int, but the compiler doesn't know about that. Its behavior is defined in terms of the standard integer types, not the library's extensions.Erupt
S
4

Yes, integer promotion* happens.

You can verify it using the std::same_as type trait to query the type of your expression:

static_assert(std::is_same_v<int, 
   decltype(std::declval<uint16_t>() * std::declval<int>())>); // b*10

Instead of casting the expression (b*10) to uint32_t, cast 10 to uint32_t:

b * uint32_t(10)

static_assert(std::is_same_v<uint32_t,
   decltype(std::declval<uint16_t>() * uint32_t(std::declval<int>()))>); // b * (uint32_t)(10)

* if int can represent all the values of the source type, otherwise, promotion to unsigned int might happen.

Swabber answered 13/3, 2023 at 16:11 Comment(1)
godbolt.org/z/n18rdbqvvFlyweight
C
1

Does uint16_t gets promoted to int?

A uint16_t often goes through the usual promotions. It will convert to an int if there is no change in value (e.g. int is wider than 16-bit), otherwise it converts to an unsigned. In either case, there is no change in value and no warning is needed.


And is it safe to typecast uint16_t to uint32_t?

Yes.

Note that (uint32_t)(b * 10) is not certainly a uint16_t to uint32_t conversion.

(b * 10) happens first.

16-bit int/unsigned: Both b, 10 convert to unsigned and then the multiplication occurs, possibly truncating the product if it was mathematically more than 0xFFFF. Then the unsigned is converted to uint32_t.

32-bit int/unsigned: b is converted to an int. Multiplication occurs and there is no overflow given the range of uint16_t and 10. Then the int product is converted to uint32_t/unsigned with no value change.


When I change if (a > b) to if (a > (b * 10)) the compiler says ...

A good alternative is to perform the b * 10 using at least the type of a. This will perform the unsigned multiplication to at least the width of uint32_t.

// if (a > (b * 10))
if (a > b * UINT32_C(10))
Composite answered 13/3, 2023 at 16:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.