Previous Page
Next Page

4.1. Conversion of Arithmetic Types

Type conversions are always possible between any two arithmetic types , and the compiler performs them implicitly wherever necessary. The conversion preserves the value of an expression if the new type is capable of representing it. This is not always the case. For example, when you convert a negative value to an unsigned type, or convert a floating-point fraction from type double to the type int, the new type simply cannot represent the original value. In such cases the compiler generally issues a warning.

4.1.1. Hierarchy of Types

When arithmetic operands have different types, the implicit type conversion is governed by the types' conversion rank . The types are ranked according to the following rules:

  • Any two unsigned integer types have different conversion ranks. If one is wider than the other, then it has a higher rank.

  • Each signed integer type has the same rank as the corresponding unsigned type. The type char has the same rank as signed char and unsigned char.

  • The standard integer types are ranked in the order:

        _Bool < char < short < int < long < long long
    

  • Any standard integer type has a higher rank than an extended integer type of the same width. (Extended integer types are described in the section "Integer Types with Exact Width (C99)" in Chapter 2.)

  • Every enumerated type has the same rank as its corresponding integer type (see "Enumerated Types" in Chapter 2).

  • The floating-point types are ranked in the following order:

        float < double < long double
    

  • The lowest-ranked floating-point type, float, has a higher rank than any integer type.

  • Every complex floating-point type has the same rank as the type of its real and imaginary parts.

4.1.2. Integer Promotion

In any expression, you can always use a value whose type ranks lower than int in place of an operand of type int or unsigned int. You can also use a bit-field as an integer operand (bit-fields are discussed in Chapter 10). In these cases, the compiler applies integer promotion : any operand whose type ranks lower than int is automatically converted to the type int, provided int is capable of representing all values of the operand's original type. If int is not sufficient, the operand is converted to unsigned int.

Integer promotion always preserves the value of the operand. Some examples:

    char c = '?';
    unsigned short var = 100;

    if ( c < 'A' )        // The character constant 'A' has type int: the value
                          // of c is implicitly promoted to int for the
                          // comparison.

      var = var + 1;      // Before the addition, the value of var is promoted
                          // to int or unsigned int.

In the last of these statements, the compiler promotes the first addend, the value of var, to the type int or unsigned int before performing the addition. If int and short have the same width, which is likely on a 16-bit computer, then the signed type int is not wide enough to represent all possible values of the unsigned short variable var. In this case, the value of var is promoted to unsigned int. After the addition, the result is converted to unsigned short for assignment to var.

4.1.3. Usual Arithmetic Conversions

The usual arithmetic conversions are the implicit conversions that are automatically applied to operands of different arithmetic types for most operators. The purpose of the usual arithmetic conversions is to find a common real type for all of the operands and the result of the operation.

The usual arithmetic conversions are performed implicitly for the following operators:

  • Arithmetic operators with two operands: *, /, %, +, and -

  • Relational and equality operators: <, <=, >, >=, ==, and !=

  • The bitwise operators, &, |, and ^

  • The conditional operator, ?: (for the second and third operands)

With the exception of the relational and equality operators, the common real type obtained by the usual arithmetic conversions is generally the type of the result. However, if one or more of the operands has a complex floating-point type, then the result also has a complex floating-point type.

The usual arithmetic conversions are applied as follows:

  1. If either operand has a floating-point type, then the operand with the lower conversion rank is converted to a type with the same rank as the other operand. Real types are converted only to real types, however, and complex types only to complex.

    In other words, if either operand has a complex floating-point type, the usual arithmetic conversion matches only the real type on which the actual type of the operand is based. Some examples:

        #include <complex.h>
        // ...
        short n = -10;
        double x = 0.5, y = 0.0;
        float _Complex f_z = 2.0F + 3.0F * I;
        double _Complex d_z = 0.0;
    
        y  = n * x;           // The value of n is converted to type double.
        d_z = f_z + x;        // Only the value of f_z is converted to
                              // double _Complex.
                              // The result of the operation also has type
                              // double _Complex.
    
        f_z = f_z / 3;        // The constant value 3 is converted to float.
        d_z = d_z - f_z;      // The value of f_z is converted to the type
                              // double _Complex.
    

  2. If both operands are integers, integer promotion is first performed on both operands. If after integer promotion the operands still have different types, conversion continues as follows:

    1. If one operand has an unsigned type T whose conversion rank is at least as high as that of the other operand's type, then the other operand is converted to type T.

    2. Otherwise, one operand has a signed type T whose conversion rank is higher than that of the other operand's type. The other operand is converted to type T only if type T is capable of representing all values of its previous type. If not, then both operands are converted to the unsigned type that corresponds to the signed type T.

The following lines of code contain some examples:

    int i = -1;
    unsigned int limit = 200U;
    long n = 30L;

    if ( i < limit )
      x = limit * n;

In this example, to evaluate the comparison in the if condition, the value of i, -1, must first be converted to the type unsigned int. The result is a large positive number. On a 32-bit system, that number is 232 - 1, and on any system it is greater than limit. Hence, the if condition is false.

In the last line of the example, the value of limit is converted to n's type, long, if the value range of long contains the whole value range of unsigned int. If notfor example, if both int and long are 32 bits widethen both multiplicands are converted to unsigned long.

The usual arithmetic conversions preserve the operand's value, except in the following cases:

  • When an integer of great magnitude is converted to a floating-point type, the target type's precision may not be sufficient to represent the number exactly.

  • Negative values are outside the value range of unsigned types.

In these two cases, values that exceed the range or precision of the target type are converted as described under "The Results of Arithmetic Type Conversions," later in this chapter.

4.1.4. Other Implicit Type Conversions

The compiler also automatically converts arithmetic values in the following cases:

  • In assignments and initializations, the value of the right operand is always converted to the type of the left operand.

  • In function calls, the arguments are converted to the types of the corresponding parameters. If the parameters have not been declared, then the default argument promotions are applied: integer promotion is performed on integer arguments, and arguments of type float are promoted to double.

  • In return statements, the value of the return expression is converted to the function's return type.

In a compound assignment, such as x += 2.5, the values of both operands are first subject to the usual arithmetic conversions, then the result of the arithmetic operation is converted, as for a simple assignment, to the type of the left operand. Some examples:

    #include <math.h>       // Declares the function double sqrt( double ).

    int   i = 7;
    float x = 0.5; // The constant value is converted from double to float.

    i = x;         // The value of x is converted from float to int.

    x += 2.5;      // Before the addition, the value of x is converted to
                   // double. Afterward, the sum is converted to float for
                   // assignment to x.

    x = sqrt( i ); // Calculate the square root of i:
                   // The argument is converted from int to double; the return
                   // value is converted from double to float for assignment to x.

    long my_func( )
    {
      /* ... */
      return 0;    // The constant 0 is converted to long, the function's return
                   // type.
    }

4.1.5. The Results of Arithmetic Type Conversions

Because the different types have different purposes, representational characteristics, and limitations, converting a value from one type to another often involves the application of special rules to deal with such peculiarities. In general, the exact result of a type conversion depends primarily on the characteristics of the target type.

4.1.5.1. Conversions to _Bool

Any value of any scalar type can be converted to _Bool. The result is 0i.e., falseif the scalar value is equal to 0; and 1, or true, if it is nonzero. Because a null pointer compares equal to zero, its value becomes false on conversion to _Bool.

4.1.5.2. Conversions to unsigned integer types other than _Bool

Integer values are always preserved if they are within the range of the new unsigned typein other words, if they are between 0 and Utype_MAX, where Utype_MAX is the greatest value that can be represented by unsigned type.

For values outside the new unsigned type's range, the value after conversion is the value obtained by adding or subtracting (Utype_MAX + 1) as many times as necessary until the result is within the range of the new type. The following example illustrates the assignment of a negative value to an unsigned integer type:

    #include <limits.h>       // Defines the macros USHRT_MAX, UINT_MAX, etc.
    unsigned short  n = 1000; // The value 1000 is within the range of unsigned
                              // short;
    n = -1;                   // the value -1 must be converted.

To adjust a signed value of -1 to the variable's unsigned type, the program implicitly adds USHRT_MAX + 1 to it until a result within the type's range is obtained. Because -1 + (USHRT_MAX + 1) = USHRT_MAX, the final statement in the previous example is equivalent to n = USHRT_MAX;.

For positive integer values, subtracting (Utype_MAX + 1) as often as necessary to bring the value into the new type's range is the same as the remainder of a division by (Utype_MAX + 1), as the following example illustrates:

    #include <limits.h>       // Defines the macros USHRT_MAX, UINT_MAX, etc.
    unsigned short  n = 0;
    n = 0xFEDCBA;             // The value is beyond the range of unsigned
                              // short.

If unsigned short is 16 bits wide, then its maximum value, USHRT_MAX, is hexadecimal FFFF. When the value FEDCBA is converted to unsigned short, the result is the same as the remainder of a division by hexadecimal 10000 (that's USHRT_MAX + 1), which is always FFFF or less. In this case, the value assigned to n is hexadecimal DCBA.

To convert a real floating-point number to an unsigned or signed integer type, the compiler discards the fractional part. If the remaining integer portion is outside the range of the new type, the result of the conversion is undefined. Example:

    double x = 2.9;

    unsigned long n = x;             // The fractional part of x is simply lost.

    unsigned long m = round(x);      // If x is non-negative, this has the
                                     // same effect as m = x + 0.5;

In the initialization of n in this example, the value of x is converted from double to unsigned long by discarding its fractional part, 0.9. The integer part, 2, is the value assigned to n. In the initialization of m, the C99 function round( ) rounds the value of x to the nearest integer value (whether higher or lower), and returns a value of type double. The fractional part of the resulting double value3.0 in this caseis thus equal to zero before being discarded through type conversion for the assignment to m.

When a complex number is converted to an unsigned integer type, the imaginary part is first discarded. Then the resulting floating-point value is converted as described previously. Example:

    #include <limits.h>         // Defines macros such as UINT_MAX.
    #include <complex.h>        // Defines macros such as the imaginary
                                // constant I.

    unsigned int  n = 0;
    float _Complex  z = -1.7 + 2.0 * I;

    n = z;                      // In this case, the effect is the same as
                                // n = -1;
                                // The resulting value of n is UINT_MAX.

The imaginary part of z is discarded, leaving the real floating-point value -1.7. Then the fractional part of the floating-point number is also discarded. The remaining integer value, -1, is converted to unsigned int by adding UINT_MAX +1, so that the value ultimately assigned to n is equal to UINT_MAX.

4.1.5.3. Conversions to signed integer types

The problem of exceeding the target type's value range can also occur when a value is converted from an integer type, whether signed or unsigned, to a different, signed integer type; for example, when a value is converted from the type long or unsigned int to the type int. The result of such an overflow on conversion to a signed integer type, unlike conversions to unsigned integer types, is left up to the implementation.

Most compilers discard the highest bits of the original value's binary representation and interpret the lowest bits according to the new type. As the following example illustrates, under this conversion strategy the existing bit pattern of an unsigned int is interpreted as a signed int value:

    #include <limits.h>         // Defines macros such as UINT_MAX
    int i = UINT_MAX;           // Result: i = -1 (in two's complement
                                // representation)

However, depending on the compiler, such a conversion attempt may also result in a signal being raised to inform the program of the value range overflow.

When a real or complex floating-point number is converted to a signed integer type, the same rules apply as for conversion to an unsigned integer type, as described in the previous section.

4.1.5.4. Conversions to real floating-point types

Not all integer values can be exactly represented in floating-point types. For example, although the value range of the type float includes the range of the types long and long long, float is precise to only six decimal digits. Thus, some long values cannot be stored exactly in a float object. The result of such a conversion is the next lower or next higher representable value, as the following example illustrates:

    long  l_var = 123456789L;
    float f_var = l_var;           // Implicitly converts long value to float.

    printf("The rounding error (f_var - l_var) is %f\n", f_var - l_var);

Remember that the subtraction in this example, like all floating-point arithmetic, is performed with at least double precision (see "Floating-Point Types" in Chapter 2). Typical output produced by this code is:

    The rounding error (f_var - l_var;) is 3.000000

Any value in a floating-point type can be represented exactly in another floating-point type of greater precision. Thus when a double value is converted to long double, or when a float value is converted to double or long double, the value is exactly preserved. In conversions from a more precise to a less precise type, however, the value being converted may be beyond the range of the new type. If the value exceeds the target type's range, the result of the conversion is undefined. If the value is within the target type's range, but not exactly representable in the target type's precision, then the result is the next smaller or next greater representable value. The program in Example 2-2 illustrates the rounding error produced by such a conversion to a less-precise floating-point type.

When a complex number is converted to a real floating-point type, the imaginary part is simply discarded, and the result is the complex number's real part, which may have to be further converted to the target type as described in this section.

4.1.5.5. Conversions to complex floating-point types

When an integer or a real floating-point number is converted to a complex type, the real part of the result is obtained by converting the value to the corresponding real floating-point type as described in the previous section. The imaginary part is zero.

When a complex number is converted to a different complex type, the real and imaginary parts are converted separately according to the rules for real floating-point types.

    #include <complex.h>        // Defines macros such as the imaginary
                                // constant I
    double _Complex dz = 2;
    float _Complex fz = dz + I;

In the first of these two initializations, the integer constant 2 is implicitly converted to double _Complex for assignment to dz. The resulting value of dz is 2.0 + 0.0 x I.

In the initialization of fz, the two parts of the double _Complex value of dz are converted (after the addition) to float, so that the real part of fz is equal to 2.0F, and the imaginary part 1.0F.


Previous Page
Next Page