What is widening , truncation , promotion , and conversion between , the different C , arithmetic data types ?

 
The arithmetic data types in C , are the integer types , such as int or unsigned long , and the floating point types , such as float , or long double .

Conversion of the integer types

What is widening ?

Widening only applies to the signed and unsigned integer types . It does not apply to other types , such as float or double .

Widening is not about converting from signed to unsigned , or from unsigned to signed , it is about expanding the signedness of an integer type , from a smaller type to a larger type , so from a smaller number of bits , to a larger one . The signedness of the integer type does not change .

For the signed type , widening is done by what is called : sign extension . If the value of the signed type is negative , it is extended by filling the new bits by the value 1 , if the value of the signed type is nonnegative , than it is extended by filling the newly allocated bits , with 0 .

For the unsigned type , the newly allocated bits are filled with 0 .

What is truncation ?

Truncation happens only for the integer types , that have the same signedness , when passing from a larger integer type , to a smaller integer type , such as , when passing from int to char , or from unsigned int to unsigned char.

Be it is signed or unsigned , the larger type is made to fit the smaller type , by discarding the bits , of the larger type , that lies outside of the width of the smaller type , keeping only the lower order bits .

Truncation as described , which is keeping only a limited number of bits , doesn’t happen when converting from a floating point type , to an integer type , or between floating point types .
When converting from a floating type , to an integer type , the fractional part is discarded , the floating type bits are not made to fit the integer type width , but the floating type is transformed from a floating point representation , to an integer representation .

The integer conversion procedure

Conversion of the integer types , consists of either performing widening or truncation first , and later reinterpreting the bits .
So first truncation or widening is performed . Truncation or widening does not change the signedness of the type , the type is only made to fit , a larger or narrower width , of the same signedness .
After having performed widening or truncation , the gotten bits in the target width , are only reinterpreted , as belonging to a new integer type , the target type .

The only exception to this rule , is when converting to the _Bool type , any nonzero value is converted to 1 , and any zero value , is converted to 0 .

Having explained , how conversion happens for the integer types , let us explain the concept of integer rank , before explaining when conversion to another integer type , takes place .

What is a rank ?

Each integer type in C has a rank .

unsigned , and signed integer types of the same type , disregarding the signedness , have the same rank . For example int , and unsigned int , have the same rank .

The order of the ranks of the integer types is , as follow :

  1. _Bool < signed char < short < int < long < long long

The rank of any standard integer type , outlined above , is larger than the rank of any implementation defined , extended integer type , having the same width .

The char , and signed char integer types , have the same rank .

The rank of an enum type is equal to its assigned implementation , defined integer type .

Integer promotion

Integer types , which have a rank smaller than int , each is promoted , to either the int type , if the int type can represent all its possible values , or to the unsigned int type , in the other case .

Integer types having a rank smaller than int , are promoted when an operator expects , that one of its operand , to be of an arithmetic type . Arithmetic types in C , are the integer or floating point types . An example of such an operator , is the subtraction operator - , or the unary negation operator - .

Integer promotion , is not how the integer is converted to another type , it is when a integer is converted to another type , in this case it is because it has a lower rank than int .

  1. unsigned char x = 1;
  2. signed char y = -x;
  3. /*
  4. x , is an unsigned char type ,
  5.   it has a rank lower than int .
  6. The unary negation operator is
  7.   used , as such x must be promoted .
  8. An int on 32 bits systems , have a
  9.   typical width of 32 bits , as such
  10.   it can store all the possible values
  11.   of an unsigned char , which is limited
  12.   to only 8 bits . As such the target
  13.   promotion type is an int .
  14. Now that the target type is decided ,
  15.   which is an int , it has a larger
  16.   width than an unsigned char , as
  17.   such widening must be performed .
  18. Widening does not change  signedness ,
  19.   so the unsigned char is widened to an
  20.   unsigned int , by using zero fill .
  21. The resulting value is 1 ,  and has a bit
  22.   representation of :
  23.   00000000000000000000000000000001
  24. The resulting bits , are interpreted as if ,
  25.    as being of the type signed int , and the
  26.    negation operator is applied .
  27. The result of the negation operator is -1 ,
  28.    which , has a bit representation of :
  29.     11111111111111111111111111111111
  30. The value is to be stored in a signed
  31.   char type , both int and signed char ,
  32.   have the same signedness , as such
  33.   truncation is applied .
  34. The width of a signed char type is
  35.   8 bits , as such the leading 24 bits
  36.   are discarded , and the result is :
  37.   11111111 , which is equal to -1 . */

Function call or return value

When making a function call , and an argument is of an integer type , different from the target parameter integer type , type conversion occurs .

The argument is converted to the parameter type , by first truncation , or widening , to the same width , and later on reinterpreting the bits as of the target parameter type .

  1. #include<stdio.h>

  2. void trivialFunction( unsigned int val){
  3.   if( val == 4294967295){
  4.     printf( "%u is equal to 4294967295\n" , val);
  5.   }
  6.   else{
  7.     printf( "%u is different from 4294967295\n" , val);}}


  8. int main( void){
  9.   signed char x = -1 ;  
  10.   trivialFunction( x);
  11.   unsigned char y = x ;
  12.   trivialFunction( y);}

  13. /* Output
  14. 4294967295 is equal to 4294967295
  15. 255 is different from 4294967295
  16. */

  17. /*
  18. In the first call to trivialFunction ,
  19.   the passed argument is of type signed
  20.   char . trivialFunction expects its
  21.   argument , to be of the type unsigned
  22.   int , as such the argument must be
  23.   converted .
  24. First widening to the same width of
  25.   unsigned int takes place .
  26. x is a signed char , as such it is
  27.   widened to a signed int , by sign
  28.   extension . The value of x is -1 ,
  29.   and it has a bit representation of
  30.   11111111 . The resulting value is -1 ,
  31.   and it has a bit representation of
  32.   11111111111111111111111111111111 .
  33. This bit pattern , is next reinterpreted
  34.   as an unsigned int , so
  35.   11111111111111111111111111111111 ,  
  36.   as an unsigned int , has a value of
  37.   4294967295 . This is why trivialFunction
  38.   prints : 4294967295 is equal to 4294967295


  39. The variable y , has a type of unsigned
  40.   char . It is assigned the value of x ,
  41.   which is a signed char .
  42. Both x , and y have the same width , as such
  43.   no widening occurs , only the bits of x ,
  44.   are reinterpreted as being unsigned .
  45. The bits of x , has a value of -1 , which is
  46.   11111111 , when reinterpreted as unsigned ,
  47.   this yield the value of 255 .
  48. Next trivialFunction is called with y ,
  49.   as a parameter .
  50. Widening occurs  , because y is an unsigned
  51.   char , and the function parameter is an
  52.   unsigned int . It is done by using
  53.   zero fill .
  54. Hence the value of 1 which has a bit
  55.   representation of 11111111 , is widened
  56.   to the value 255 which has a bit
  57.   representation of :
  58.   00000000000000000000000011111111
  59. The function prints :
  60.   255 is different from 4294967295
  61. */

When the return value of a function is different from its return type , the return value is converted to the function’s return type .

Assignment and initialization

When performing assignment or initialization to an integer variable , using the = operator , and the expression to be assigned , is of a different integer type , integer conversion takes place .

  1. unsigned char x = 1 ;
  2. /*
  3. 1 is an integral literal ,  it is
  4.   of the type int .
  5. The type int has a typical width
  6.   of 32 bits , while an unsigned
  7.   char has a typical width
  8.   of 8 bits .
  9. The bits of the int type
  10.   00000000000000000000000000000001
  11.   are reinterpreted as  being the
  12.   bits of an unsigned int type .
  13.   The result is :
  14.   00000000000000000000000000000001
  15. The unsigned int value is truncated to
  16.   8 bits , and the value gotten is
  17.   00000001 , which is assigned to
  18.   the unsigned char x .*/

For further information , about the type of integer literals , you can check this article .

Arithmetic operators

When one of the following operators , is being used , an integer conversion will occur .

  1. * / % + -  
  2. /*
  3. Multiplication , division ,
  4.   modulos , addition ,
  5.  subtraction  .*/


  6. < <= > >= == !=  
  7. /*
  8.  Relational less , less or equal ,
  9.   larger , larger or equal ,
  10.   equal , not equal .*/


  11. & ^ |
  12. /*
  13. Bitwise and , xor , or .*/


  14. ?:
  15. /*
  16. The Ternary operator expression ,
  17.   must return a value of a specific
  18.   type , the second and third operand ,
  19.   are converted to a same type . */


  20.  +=  -=  *=  /=  %=  &=   ^=  |=  
  21. /* operate and assign operators  */

In the case of these operators , a common type for the operands and the result must be determined . This is done as specified by the following table .

Unsigned Signed Rank Operands and Result type
uT sT uT >= sT uT
uT sT uT < sT sT , if sT is capable of holding , all the possible values of : uT .

The unsigned type of sT , if sT is not capable of holding all the possible values of : uT .
uT , means an unsigned type , such as : unsigned int
sT , means a signed type , such as :int
The result of relational operators such as < , is always 0 for true , and 1 for false , and it is always of the type int .
  1. int si = -1 ;
  2. unsigned int ui = 0 ;

  3. ui = ui + si ;
  4. /*
  5. int , and unsigned int , have the same
  6.   rank , one is signed , and the other
  7.   is unsigned , as such both operands ,
  8.   must be converted to the unsigned int
  9.   type , and the result of the operation ,
  10.   is of an the unsigned int type .
  11. ui is unsigned , so no conversion is
  12.   necessary .
  13. To convert si , to the unsigned int type ,
  14.   and since both si and ui have the same
  15.   width , the bits of si , which are
  16.   11111111111111111111111111111111 ,
  17.   are kept as is , they are only reinterpreted
  18.   as belonging to a signed type , so now
  19.   they have a value of 4294967295 .
  20. The addition is performed , and the result
  21.   of the addition operation , is as such :
  22.   4294967295 + 0 = 4294967295 , and is
  23.   of the type unsigned int .
  24. ui is of type unsigned int , as such
  25.   no conversion is necessary , and the
  26.   result of 4294967295 is stored in ui .
  27. */


  28. long long lli = -1 ;
  29. ui = lli + ui ;
  30. /*
  31. lli is of the type long long ,
  32.   it has a higher rank than
  33.   unsigned int . long long
  34.   can hold all the values of
  35.   unsigned int , as such ,
  36.   both operands must be of
  37.   the type long long .
  38. lli is of the type long long ,
  39.   so no conversion is necessary .
  40. ui is an unsigned int , it has
  41.   a bit representation of
  42.   11111111111111111111111111111111
  43.   it is first extended to unsigned
  44.   long by using zero fill .
  45.   0000000000000000000000000000000011111111111111111111111111111111
  46.   After that , the gotten bits are
  47.   reinterpreted as being of type
  48.   long long . The gotten value , is
  49.   the same as the value of ui , which
  50.   is 4294967295 .  
  51. The addition is  performed between ,
  52.   the two long long integer types , and
  53.   the result is -1 + 4294967295 , which
  54.   is equal to 4294967294 , and is of
  55.   the type long long .
  56. ui is of the type unsigned int , the result
  57.   which is of the type long long  must
  58.   be converted to unsigned int  .
  59. It is first converted to unsigned long long ,
  60.   the bits pattern does not change ,  so it remains
  61.   0000000000000000000000000000000011111111111111111111111111111110 .
  62.   Now that is is of the type unsigned long long ,
  63.   it is truncated to the type of ui , which is
  64.   unsigned int , and it has the format :
  65.   11111111111111111111111111111110  ,
  66.   and a value of : 4294967294 .
  67. */


  68. long int li  = -1 ;
  69. li = li + ui ;
  70. /*
  71. ui is an unsigned int , whereas li
  72.   is a long int . A long int has
  73.   a higher rank than an unsigned
  74.   int . Assuming that on this machine ,
  75.   both long int and unsigned int have
  76.   a width of 32 bits , this means
  77.   that long int is not capable of
  78.   representing all the possible values
  79.   of the unsigned int type , as such
  80.   the operands , and the result
  81.   must be of the unsigned long type .
  82. li bits are kept as is , and only
  83.   reinterpreted as being of the
  84.   type unsigned long , as such li
  85.   will have the value of 4294967295 .
  86. ui is converted to unsigned long , by
  87.   widening. unsigned int , and unsigned long
  88.   have the same width , as such the bits
  89.   of ui remains the same ,
  90.   11111111111111111111111111111110 .
  91. Adding 4294967295 + 4294967294 =
  92.   8589934589 . The result is of
  93.   the type unsigned long , it cannot
  94.   fit the width of the unsigned
  95.   long type , which has a max
  96.   value of 4294967295 , as such
  97.   overflow has occurred .
  98.   The modulo of the result , with
  99.   regards to 2 to the power of
  100.   the number of bits ,
  101.   of the unsigned long type is taken .
  102.   This is equal to
  103.   8589934589 % 4294967296 =
  104.     4294967293 , which is
  105.   11111111111111111111111111111101
  106. The result must be stored in li ,
  107.   li is of the long type , the gotten
  108.   value is of the type unsigned long ,
  109.   the bits are kept as is , and the
  110.   result is only reinterpreted as being
  111.   a signed long , as such the value
  112.   of li is -3 .
  113. */

For information on overflow of the signed , and unsigned integer types , you can check this , and this article .

Cast operator

Explicit conversion happens , when using the casting operator : (Type) expression . As an example , explicitly casting the int literal 1 , to the long type .

  1. (long) 1

When using the cast operator , this is called explicit casting , all other cases of conversion , are called implicit casting .

Conversion of the floating point types

Converting from one floating type , to another

Each floating point type , has a different range , and precision . As such , when passing from floating point types , with higher precision and range , to floating point types with smaller precision and range , a loss of precision , an underflow , or an overflow , can occur .

  1. double dbl = 16777217 ;
  2. float fl = dbl ;
  3. /*
  4. Loss of precision , fl value is
  5.   16777216 .*/


  6. dbl = 1.2e-50 ;
  7. fl = dbl ;
  8. /*
  9. Underflow occurs , the behavior
  10.   is implementation defined ,
  11.   in this case , fl has a value
  12.   of 0 .*/

  13. dbl = 3.4e100;
  14. fl = dbl ;
  15. /*
  16. Overflow occurs , the behavior
  17.   is implementation defined ,
  18.   fl has a value
  19.   of positive infinity */

When passing from a floating point type , with a smaller range and precision , to a floating point type , with a higher range and precision , no precision loss , underflow , or overflow , occurs . The precision and range are preserved .

  1. float fl = 1.099511627776e12f;
  2. double dbl = fl ;
  3. /*
  4. precision and range are
  5.   preserved , dbl has a
  6.   value of :
  7.   1.099511627776e12f ,
  8.   which is 2 to the
  9.   power 40 .*/

Converting from a floating point type , to an integer type

The process of converting a floating point type , to an integer type , can be thought of , as if , the number is first converted to the decimal notation , then the fractional part is discarded , then it is represented in its signed or unsigned representation .

  1. double dbl = 1.3f;
  2. unsigned char uc = (unsigned char) x ;  
  3. // uc is equal to 1

  4. dbl  = -1.3f;
  5. uc = dbl  ;
  6. // uc is equal to 255

If the floating point number , is too large to be represented in an integer type , the behavior is not defined by the C standard , it is defined by the implementation .

  1. double dbl = 3.4E30 ;

  2. unsigned long ul  = dbl ; //ul is equal to 0
  3. unsigned int ui  = dbl ; //ui is equal to 0
  4. unsigned char uc  = dbl ; //uc is equal to 0

  5. long li  = dbl ; //li is equal to -9223372036854775808
  6. int si  = dbl ; //si is equal to -2147483648
  7. signed char sc  = dbl ; //sc is equal to 0

Converting from an integer type , to a floating point type

When converting an integer type , to a floating point type , the integer value can always be represented , but there might be a loss of precision .

  1. int si = 16777216 ;
  2. float fl = si ;  // fl is equal to 16777216

  3. si = 16777217 ;
  4. float fl = si ; // fl is equal to 16777216

When does the conversion occurs ?

When a floating point type is involved , conversion occurs , when a function call is made , and the passed argument is of a different type than the function parameter , or when the return value of a function , is of different type , than its return type .

It also happens , when performing floating point variables , assignment and initialization . Finally , it occurs when using arithmetic operators , or when done explicitly , using explicit casting , such as (float) 1.0 .

Rank of floating point types

The rank of a floating point type , is always higher than the rank of an integer type , so an integer type , is always converted to a floating point type .

As for the floating point types , they have the following ranks :

  1. float < double < long double

If an arithmetic operation , involves two floating point types of different ranks , the floating point type with the lower rank , is converted to the floating point type with the higher rank .

The type of the result of an operation involving a floating point type , is the same type , as the one determined by the ranking algorithm .

Floating point types promotion

As with the integer type promotion , when performing arithmetic operations , a floating point type can be promoted , to a type with a higher precision and range .

This is not to be confused with how conversion happens , or in which rank , an arithmetic operation involving a floating point type , is to be performed , It is always performed using the higher operand rank .

The promotion rule , is defined in the macro FLT_EVAL_METHOD , defined in the header float.h .

If FLT_EVAL_METHOD is set to 0 , then arithmetic operations are done in the type of the widest operand , so if both operands are float , the arithmetic operation is done using the float type , so no promotion occurs .

If FLT_EVAL_METHOD is set to 1 , then arithmetic operations are performed , by promoting the operands to long double , if any operand is of the long double type , otherwise operands are promoted to the double type , even if both operands are of the float type .

If FLT_EVAL_METHOD is set to 2 , then arithmetic operations are performed , by promoting the operands , to the long double type .

If FLT_EVAL_METHOD is set to -1 , then the behavior is not defined .