7

I need to take 2 unsigned 8-bit values and subtract them, then add this value to a 32-bit accumulator. The 8-bit subtraction may underflow, and that's ok (unsigned int underflow is defined behavior, so no problems there).

I would expect that static_cast<uint32_t>(foo - bar) should do what I want (where foo and bar are both uint8_t). But it would appear that this casts them first and then performs a 32-bit subtraction, whereas I need it to underflow as an 8-bit variable. I know I could just mod 256, but I'm trying to figure out why it works this way.

Example here: https://ideone.com/TwOmTO

uint8_t foo = 5;
uint8_t bar = 250;

uint8_t diff8bit = foo - bar;
uint32_t diff1 = static_cast<uint32_t>(diff8bit);

uint32_t diff2 = static_cast<uint32_t>(foo) - static_cast<uint32_t>(bar);

uint32_t diff3 = static_cast<uint32_t>(foo - bar);

printf("diff1 = %u\n", diff1);
printf("diff2 = %u\n", diff2);
printf("diff3 = %u\n", diff3);

Output:

diff1 = 11
diff2 = 4294967051
diff3 = 4294967051

I would suspect diff3 would have the same behavior as diff1, but it's actually the same as diff2.

So why does this happen? As far as I can tell the compiler should be subtracting the two 8-bit values and then casting to 32-bit, but that's clearly not the case. Is this something to do with the specification of how static_cast behaves on an expression?

Joel Geddert
  • 195
  • 6
  • 3
    This is an example of *integral promotion* via the *usual arithmetic conversions*. It is applied because of the subtraction expression, not the `static_cast`. – dyp Jan 13 '15 at 20:46
  • Why would you think diff2 would be the same as diff1? diff2 is clearly performing the subtraction using two `uint32_t`s. As for the result of diff3 - the compiler has decided it's the right thing to do (which it is, it will give you the most correct answer). If you want it to do something else you're going to have to tell it so. – mbgda Jan 13 '15 at 20:48
  • @mbgda They expect diff3 and diff1 to be the same. – Joseph Mansfield Jan 13 '15 at 20:50
  • @JosephMansfield The wording of *"would suspect diff2 would have the same behavior as diff1, but it's actually the same as diff3"* is a little confusing but you must be right. – mbgda Jan 13 '15 at 20:52
  • @mbgda But that's not what it says. :S – Joseph Mansfield Jan 13 '15 at 20:52
  • @JosephMansfield Weird - looks like it was edited. I have two versions of this question open in two tabs and they say two different things. – mbgda Jan 13 '15 at 20:55
  • Yeah, sorry, I screwed it up the first time. I edited it about a minute later, but apparently that wasn't fast enough! – Joel Geddert Jan 13 '15 at 21:07

2 Answers2

8

For most of the arithmetic operators (including -), the operands undergo the usual arithmetic conversions. One of these conversions is that any value of type narrower than int is promoted to int. (Standard reference: [expr]/10).

So the expression foo - bar becomes (int)foo - (int)bar giving (int)-245. Then you cast that to uint32_t which will give a large positive number.

To get the result you are intending , cast to uint8_t instead of uint32_t. Alternatively, use the modulus operator % on the result of the cast to uint32_t.

It is not possible to do a calculation directly in narrower precision than int

M.M
  • 138,810
  • 21
  • 208
  • 365
  • 1
    Interesting, thanks! So if this had been done on an 8-bit CPU, it would have given the result I had originally expected? – Joel Geddert Jan 13 '15 at 21:09
  • 1
    @KeytarHero the C++ standard specifies that `int` is at least 16-bit (on an 8-bit CPU then the compiler will have to use some register pairs or whatever to satisfy the standard requirements) – M.M Jan 13 '15 at 21:22
4

The issue is not the static_cast but the subtraction, the operands of additive operators have the usual arithmetic conversions applied to them and in this case the integral promotions which results in both operands of the subtraction being promoted to int:

static_cast<uint32_t>(foo - bar);
                      ^^^   ^^^

On the other hand:

static_cast<uint8_t>(foo - bar);

would produce desired result.

from the draft C++ standard section 5.7 [expr.add] says:

The additive operators + and - group left-to-right. The usual arithmetic conversions are performed for operands of arithmetic or enumeration type.

this results in the integral promotions, section 5 [expr] says:

Otherwise, the integral promotions (4.5) shall be performed on both operands

which results in both operands being converted to int, section 4.5 [conv.prom] says:

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.

and then the static_cast to uint32_t is applied which results in a conversion which is defined as follows in section 4.7 [conv.integral]:

If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source integer (modulo 2n where n is the number of bits used to represent the unsigned type). [

The questions Why must a short be converted to an int before arithmetic operations in C and C++? explains why types smaller than int are promoted for arithmetic operations.

Community
  • 1
  • 1
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740