Introduction

C lets you mix types freely in an expression: char with int, int with unsigned, int with double. Before any arithmetic happens, the compiler silently converts the operands to a common type, following fixed rules. Most of the time the result is what you expect. When signed and unsigned values meet, or when a value is too big for its destination, the result can be startling: in C, -1 < 1u is false.

This note gives the conversion rules in the order the compiler applies them, then the conversions that happen on assignment, in function calls and with casts.

Conventions: C11, LP64 (32-bit int, 64-bit long and size_t), two's complement, IEEE 754 float and double.

Prerequisites

Core concepts

Implicit and explicit conversions

  • An implicit conversion is inserted by the compiler: in arithmetic, in comparisons, on assignment, on passing an argument and on returning a value.
  • An explicit conversion is a cast, (type) expression. A cast is a unary operator with the precedence of the other unary operators, so (double)x / y converts only x, and then the division is done in double.

Integer conversion rank

Every integer type has a rank: _Bool < char = signed char = unsigned char < short < int < long < long long. An unsigned type has the same rank as its signed counterpart. Rank, not size, drives the rules (although on most systems a higher rank also means at least as many bits).

Step 1: the integer promotions

In arithmetic, any operand whose type has rank below int (char, short, their signed and unsigned versions, _Bool, and bit-fields) is converted to int if int can represent every value of the original type, and to unsigned int otherwise. On every common system int is wider than short, so char and short always promote to int.

Consequences:

  • unsigned char a = 200, b = 100; int s = a + b; gives 300: the addition is done in int, not in 8 bits.
  • sizeof('a' + 'b') and sizeof(sh + sh) for a short sh are both sizeof(int), which is 4.
  • ~ and - on a small unsigned type act on the promoted int (Bitwise Operators).

Step 2: the usual arithmetic conversions

For binary operators such as + - * / % < > == & | ^ and ?: (its second and third operands), after the promotions the two operands are brought to one common type:

  1. If either operand is long double, convert the other to long double. Otherwise, if either is double, convert to double. Otherwise, if either is float, convert to float.
  2. Otherwise both are integers. Apply the integer promotions to both, then:
    • 1. If both have the same type, stop.
    • 2. If both are signed, or both unsigned, convert the one of lower rank to the type of higher rank.
    • 3. If the unsigned operand has rank greater than or equal to the signed one, convert the signed operand to the unsigned type.
    • 4. Otherwise, if the signed type can represent every value of the unsigned type, convert the unsigned operand to the signed type.
    • 5. Otherwise convert both to the unsigned type corresponding to the signed operand's type.

The shifts << and >> are an exception: they apply only the integer promotions, separately, and the result has the promoted type of the left operand.

Ladder of the usual arithmetic conversions
Figure 1. The usual arithmetic conversions as a ladder: floating types on top, then the integer ranks with each signed type beside its unsigned counterpart; small types first promote to int, and a signed operand meets an unsigned operand of equal or higher rank by becoming unsigned.

Converting a signed value to unsigned

When a value is converted to an unsigned type that cannot hold it, the result is the value reduced modulo 2n2^n (C11 6.3.1.3), where nn is the width of the unsigned type. For −1-1 and a 32-bit unsigned int: −1+232=4294967295-1 + 2^{32} = 4294967295, the largest unsigned int. That is why:

  • -1 < 1u is 0. Rule 2.3 converts −1-1 to unsigned int, giving 4294967295, which is not less than 1.
  • int i = -1; unsigned u = 1; i < u is 0 for the same reason.
  • sizeof(int) > -1 is 0. sizeof yields size_t (unsigned long on LP64), so −1-1 becomes 264−12^{64} - 1. (int)sizeof(int) > -1 is 1.
  • unsigned u = 1; u - 2 is 4294967295, not −1-1.

When the answer depends on the data model

Rule 2.4 makes some results differ between systems. Compare -1L < 1u:

  • LP64: long (64 bits, signed) has higher rank than unsigned int (32 bits) and can represent all its values. Rule 2.4 converts 1u to long; the comparison is −1<1-1 < 1, which is 1.
  • ILP32: long and unsigned int are both 32 bits. long cannot represent 4294967295, so rule 2.5 converts both to unsigned long; −1-1 becomes 4294967295 and the result is 0.

Conversions to a narrower or different type

FromToResult
any integerunsigned type too smallvalue modulo 2n2^n: well defined
any integersigned type too smallimplementation-defined result or an implementation-defined signal (gcc: modulo 2n2^n, so (char)200 is −56-56 when char is signed)
floatingintegertruncated toward zero: (int)3.99 is 3, (int)-3.99 is −3-3; if the truncated value is out of range, undefined
integerfloatingexact if representable; otherwise the next higher or next lower representable value, chosen in an implementation-defined way (IEEE round-to-nearest in practice). float f = 16777217; stores 16777216, because 224+12^{24} + 1 needs 25 significant bits and float has 24
doublefloatrounded if in range; undefined if out of range
any scalar_Bool0 if the value compares equal to 0, otherwise 1

Signed overflow is not a conversion. INT_MAX + 1 is computed in int and overflows: undefined behaviour. Converting the large value 4294967295 to int is a conversion: implementation-defined. The distinction matters in "what is printed" questions.

Conversions on assignment, calls and returns

  • Assignment converts the right operand to the type of the left operand, with the rules in the table above. unsigned char c = 200 + 100; stores 300 mod 256=44300 \bmod 256 = 44.
  • Calls to a prototyped function convert each argument to the parameter type, as if by assignment.
  • Calls with no prototype, and the variable arguments of printf, get the default argument promotions: integer promotions, and float becomes double. That is why %f prints both float and double, and why %d for a char argument is correct.
  • Return converts the returned expression to the function's return type.

The arithmetic type is chosen before the result is stored

The type of an expression depends only on its operands, never on where the result goes.

  • long big = 100000 * 100000; multiplies two ints; 101010^{10} does not fit in 32 bits, so this is signed overflow (undefined), even though long could hold the result. Write 100000L * 100000, which gives 10000000000.
  • double avg = sum / n; with int sum = 7, n = 2 gives 3.0; the division was integer division. (double)sum / n gives 3.5; (double)(sum / n) gives 3.0 because the cast is applied after the division.

Worked examples

All outputs below are for C11 with gcc on x86-64 Linux (LP64).

Example 1.

C
unsigned u = 1; int i = -1;
printf("%d %d %d\n", i < u, -1 < 1u, (int)sizeof(int) > -1);   /* 0 0 1 */
printf("%d %d\n", sizeof(int) > -1, -1L < 1u);                  /* 0 1 */

Example 2 (promotion then narrowing).

C
unsigned char a = 200, b = 100;
int s = a + b;              /* 300: computed in int */
unsigned char c = a + b;    /* 300 mod 256 = 44 */
short sh = 30000;
int t = sh + sh;            /* 60000: no short overflow, the sum is an int */

Example 3 (floating to integer and casts). With int x = 7, y = 2: (int)3.99 is 3, (int)-3.99 is −3-3, (int)(7 / 2.0) is 3; (double)x / y is 3.50, (double)(x / y) is 3.00, x / (double)y is 3.50.

Example 4 (signed narrowing, implementation-defined). On gcc, char ch = (char)200; gives −56-56 (200−256200 - 256). Converting (signed char)-1 to unsigned char gives UCHAR_MAX; this is 255 when CHAR_BIT=8. Integer-to-unsigned conversion is modulo one more than the target maximum.

Example 5 (an unsigned loop that never ends).

C
for (unsigned k = n; k >= 0; k--)   /* k >= 0 is always true */
    ...

When k is 0, k-- makes it 4294967295 and the loop continues. gcc warns "comparison of unsigned expression in '>= 0' is always true". The same trap appears with strlen(s) - 1 when s is empty: strlen returns size_t, so the result is 264−12^{64} - 1, not −1-1.

Important properties and observations

  • Arithmetic is never done in a type narrower than int.
  • A signed operand meeting an unsigned operand of the same or higher rank becomes unsigned; negative values become huge.
  • Whether long versus unsigned int comparisons are signed or unsigned depends on the data model.
  • Floating to integer truncates toward zero; out of range is undefined.
  • Integer to float can lose precision above 2242^{24}; integer to double above 2532^{53}.
  • The destination of an assignment does not influence the type in which the right side is computed.
  • Narrowing to unsigned is always well defined; narrowing to signed is implementation-defined; signed overflow during arithmetic is undefined.

GATE examination perspective

  • Signed versus unsigned comparison is a trap: if (-1 < sizeof(int)) takes the false branch.
  • Character arithmetic. Assuming an ASCII-compatible execution character set, char c = 'A' + 1; is 'B', and printf("%d", c) prints 66. ISO C does not guarantee consecutive letter codes or ASCII numeric values.
  • Overflow of small types. For integer-to-integer conversion, 300 becomes 300 mod (UCHAR_MAX+1)300\bmod(\mathrm{UCHAR\_MAX}+1) in unsigned char, and -1 becomes UCHAR_MAX. The values 44 and 255 respectively require CHAR_BIT=8 (UCHAR_MAX=255); they are not universal implementation values. Assigning it to a signed char gives an implementation-defined result; gcc reduces modulo 256, giving 44 for 300 and −56-56 for 200.
  • Integer division before conversion. float f = 5 / 2; stores 2.0.
  • Data model questions. When a question gives sizes, apply rules 2.3 to 2.5 with those sizes.

Summary

Sources

  1. ISO/IEC 9899:2011 (C11), committee draft N1570, (consulted 2026-11-09), Section 6.3.1.1 (integer rank and promotions), 6.3.1.3 (signed and unsigned integers), 6.3.1.4 (real floating and integer), 6.3.1.8 (usual arithmetic conversions), 6.5.2.2 (function calls, default argument promotions), 6.5.4 (cast operators), 6.5.7 (shift operators).
  2. B. W. Kernighan and D. M. Ritchie, The C Programming Language, 2nd ed., Prentice Hall, 1988: Section 2.7 (type conversions).

Next steps