7

I am quite confused by the following code:

#include <stdio.h>
#include <stdint.h>

int main(int argc, char ** argv)
{
    uint16_t a = 413;
    uint16_t b = 64948;

    fprintf(stdout, "%u\n", (a - b));
    fprintf(stdout, "%u\n", ((uint16_t) (a - b)));

    return 0;
}

That returns:

$ gcc -Wall test.c -o test
$ ./test
4294902761
1001
$ 

It seems that expression (a - b) has type uint32_t. I don't uderstand why since both operators are uint16_t.

Can anyone explain this to me?

unwind
  • 391,730
  • 64
  • 469
  • 606
Julien REINAULD
  • 599
  • 2
  • 5
  • 18
  • You are declaring both as unsigned ints, and then doing a subtraction that will result in a negative number. You cannot expect reliable behavior when you are misusing datatypes. – onit Oct 31 '11 at 14:04
  • 1
    Actually that's exactly what I need to do. – Julien REINAULD Oct 31 '11 at 14:11
  • please explain yourself when downvoting. – MK. Oct 31 '11 at 14:15
  • I'm not the down-voter, but there's no requirement that people "explain themselves". If there were, it would not be possible to down-vote without a comment. – Stephen Canon Oct 31 '11 at 14:16
  • Sorry I am having trouble to edit... – Julien REINAULD Oct 31 '11 at 14:18
  • @JulienREINAULD Exactly what are you trying to do? What exactly are you trying to represent by subtracting two unsigned ints to get a negative number. – onit Oct 31 '11 at 14:21
  • 2
    So I was waying: Actually that's exactly what I need to do. a and b represent the values of a 16-bit counter in a piece of hardware. A 16-bit counter counts from 0 to 65535. So a and b are uint16_t. Now if I want to know the number of cyces that passed between 2 timestamps a and b, I just have do the subtraction. If counter went from 10 to 100, I know that 90 cycles have passed. I f counter want from 65530 to 2, i.e went from 65530 to 65535, then rolled back to 0, and went to 2, I know that 5 + 1 + 2 = 8 cycles passed. – Julien REINAULD Oct 31 '11 at 14:22
  • That's why I wanted to use uint16_t because I don't want negative results. Using variables of the same size as the counter allows to use simple arithmetics – Julien REINAULD Oct 31 '11 at 14:22
  • 1
    Honestly this sounds like a design issue. One, if it is possible that more than 65535 cycles pass between a check, your design is inherently flawed since you cannot even represent that number with your datatype. Also, you need somehow to know whether or not the counter has rolled back from 65535 to 0 (unless you assume if its smaller it has rolled back, but then again its broken if you have more than 65535 cycles inbetween checks). If you can guarantee that less than 65535 cycles pass between checks, just do if b > a ? b - a : (((65335 - a) + b) + 1). Might be able to optimize that as well. – onit Oct 31 '11 at 14:34
  • My counter will never roll back more than once between 2 checks. – Julien REINAULD Oct 31 '11 at 14:41
  • 2
    b - a is so much simpler than b > a ? b - a : (((65335 - a) + b) + 1) ... – Julien REINAULD Oct 31 '11 at 14:42
  • @JulienREINAULD No its not, considering you will have to format the data since it will certainly not be in the right format even if you can guarantee reliable behavior. – onit Oct 31 '11 at 15:30

3 Answers3

16

The C standard explains this quite clearly (§6.5.6 Additive Operators):

If both operands have arithmetic type, the usual arithmetic conversions are performed on them.

(§6.3.1.8 Usual Arithmetic Conversions):

... the integer promotions are performed on both operands.

(§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.

Since int can represent all values of uint16_t on your platform, a and b are converted to int before the subtraction is performed. The result has type int, and is passed to printf as an int. You have specified the %u formatter with an int argument; strictly speaking this invokes undefined behavior, but on your platform the int argument is interpreted as it's twos-complement representation, and that is printed.

Stephen Canon
  • 103,815
  • 19
  • 183
  • 269
1

If you throw away the top-end bits of a number (by the explicit cast to a 16 bit unsigned integer) then you're going to have a result that is smaller (within the range of 0 and 2^16-1) than before.

Edwin Buck
  • 69,361
  • 7
  • 100
  • 138
0

C promotes the arguments to unsigned int before doing the subtraction. This is standard behavior.

See, for instance, In a C expression where unsigned int and signed int are present, which type will be promoted to what type? for details.

Community
  • 1
  • 1
unwind
  • 391,730
  • 64
  • 469
  • 606
  • 1
    It actually promotes them to either `int` or `unsigned int` - if `int` can represent all the values of `uint16_t`, then they will be promoted to `int`. – caf Oct 31 '11 at 14:09