17

Why is the following code printing 255?

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

int main(void) {
  uint8_t i = 0;
  i = (i - 1) % 16;
  printf("i: %d\n", i);
  return 0;
}

I assumed 15, although i - 1 evaluates to an integer.

Yu Hao
  • 119,891
  • 44
  • 235
  • 294
Razer
  • 7,843
  • 16
  • 55
  • 103

3 Answers3

16

Because of integer promotions in the C standard. Briefly: any type "smaller" than int is converted to int before usage. You cannot avoid this in general.

So what goes on: i is promoted to int. The expression is evaluated as int (the constants you use are int, too). The modulus is -1. This is then converted to uint8_t: 255 by the assignment.

For printf then i is integer-promoted to int (again): (int)255. However, this does no harm.

Note that in C89, for a < 0, a % b is not necessarily negative. It was implementation-defined and could have been 15. However, since C99, -1 % 16 is guaranteed to be -1 as the division has to yield the algebraic quotient.


If you want to make sure the modulus gives a positive result, you have to evaluate the whole expression unsigned by casting i:

i = ((unsigned)i - 1) % 16;

Recommendation: Enable compiler warnings. At least the conversion for the assignment should give a truncation warning.

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
  • 4
    It's only implementation-defined in C89/C90. The linked C11 standard paragraph even defines it in the second sentence and the UB stuff isn't relevant here. – cremno Aug 14 '15 at 22:22
  • 2
    Then read the first sentence including the footnote too. Your second example (specifically `a / 16 == -1`) doesn't satisfy it. The C99 rationale (6.5.5 on page 67, 7.20.6 on 164 and 165) are also a helpful resource. – cremno Aug 14 '15 at 23:10
  • Regarding the truncation warning, `((unsigned)i - 1) % 16` is guaranteed to be in the range 0 through 15, so there *should* be no warning for assigning it to a `uint8_t` because the value cannot be out of range. – M.M Aug 15 '15 at 04:06
  • @MattMcNabb: The truncation is due to assigning a larger type to a smaller one, not about the outcome of the RHS. The compiler cannot detect all situations the expression is within valid range. Here, truncation _does_ occur, because `(int)(uint8_t)(int)-1` in fact is a truncation truncated, as it does not yield the original value anymore. Things would have been different with `int8_t`. – too honest for this site Aug 15 '15 at 14:09
  • The remainder/division I read again. Yes, my bad, I did not honour the "algebraic division" correctly (had to look that up in a dict first). It is more important than I first thought. Point taken. – too honest for this site Aug 15 '15 at 14:13
  • @cremno: Second point taken. I did not pay proper attention to the _algebraic quotient_ part. Somehow the old C90 definition shadowed that. Agree, that's quite an important difference. – too honest for this site Aug 15 '15 at 14:21
  • Not all constants are of type `int`. All *unsuffixed* constants whose values are in the range `INT_MIN` to `INT_MAX` are of type `int`. (Of course the constants `0`, `1`, and `16` in the OP's code qualify, and are of type `int`.) – Keith Thompson Jul 17 '16 at 22:29
  • @KeithThompson: Ok, got it (sorry, that was a bit ago). I think I can restrict to the constants in the question, which are definitively `int`. – too honest for this site Jul 17 '16 at 22:39
13

This is because -1 % n would return -1 and NOT n - 1 1. Since i in this case is unsigned 8 bit int, it becomes 255.

1 See this question for more details on how modulo for negative integers works in C/C++.

Community
  • 1
  • 1
John Bupit
  • 10,406
  • 8
  • 39
  • 75
  • 1
    That's true, but the OP is apparently expecting `uint8_t` to behave as *8-bit unsigned* type throughout the process of evaluation and produce `255` instead of `-1` *before* calculating the modulo. The reason `uint8_t` does not behave that way is that it is promoted to `int` first. – AnT stands with Russia Aug 14 '15 at 22:38
  • Downvoting because this doesn't explain why it's `-1 % n` instead of `255 % n` (integer promotion rules, see Olaf's more complete answer). – Peter Cordes Aug 15 '15 at 05:53
2

This works (displays 15) with Microsoft C compiler (no stdint.h, so I used a typedef):

#include <stdio.h>
typedef unsigned char uint8_t;

int main(void) {
    uint8_t i = 0;
    i = (uint8_t)(i - 1) % 16;
    printf("i: %d\n", i);
    return 0;
}

The reason for the 255 is because (i - 1) is promoted to integer, and the integer division used for % in C rounds towards zero instead of negative infinity (rounding towards negative infinity is the way it's done in math, science, and other programming languages). So for C % is zero or has the same sign as the dividend (in this case -1%16 == -1), while in math modulo is zero or has the same sign as the divisor.

rcgldr
  • 27,407
  • 3
  • 36
  • 61