Chapter 6: Arithmetic Data Type Conversions
In programming, it is very common to have arithmetic types which are different from each other. Just as the types are different, it’s a given that we cannot perform mathematical operations on inputs belonging to two different arithmetic types. When dealing with such types, we need to convert one of them into the same type as the other. In this chapter, we will learn all about these conventions and rules to arithmetic types of conversions.
However, be mindful when going through this chapter, as you will often find yourself dealing with different data types (especially arithmetic). In such cases, it is important to know how to deal with them. Moreover, data type conversions are a very important and very fundamental skill when it comes to C++ programming. This is because big programming projects often involve huge amounts of data streams from different sources and are very intricately intertwined. This means that the inputs and outputs of such a complex program begin to form an ecosystem of their own as they are used and reused by different classes, objects, and functions. As such, it is not always the case that such data being circulated through the program is going to be the correct type. This is why implementing data type conversions is very important to allow the program to work efficiently.
In this chapter, we will chiefly learn about implicit type conversions and briefly discuss some additional type conversions at the end of this chapter.
Implicit Type Conversions
Before we go into the main discussion, we need to set up the context. The following figures that have been shown below highlight the integer promotion and hierarchy of int type respectively:
(Integer Promotion)
(int type hierarchy)
In many of the C++ programs, we will occasionally see a single expression containing different arithmetic types. This mixed-up arithmetic types within the same expression mean that the operands of the corresponding operator belong to the different types that have been highlighted in the expression. The reason why this does not cause an error in the program while it tries to perform the corresponding operation is due to the compiler performing an implicit type conversion by itself.
In an implicit type conversion, the requirement of the operation which needs to be performed is first considered. This means that we first check the type needed for the operation to proceed. This type that is determined is a common ground between the two different types between which we are performing the implicit type conversion. Once a common type through which we can perform the required operation is decided, the values for both operands are assigned this type.
A general rule in implicit type conversion is that a ‘smaller ’ type is the one which is subject to conversion. The smaller type is generally converted to the larger type of the two operands. However, there is an operator that is exempt from this rule, and this operator is the assignment operator . We will discuss the assignment operator in the upcoming sections of this chapter in detail.
If an arithmetic operation is performed, then the result obtained will be in the same type as the one which was specified for the operation to be performed. Note, however, that no arithmetic operation can be performed on one value, there at least needs to be two values. On the other hand, regardless of the type of operands used, a comparison expression will always be a bool type.
Now let’s talk about the two figures shown at the beginning of this section.
Integer Promotion
In this type of conversion, the main focus is to conserve the value of the original type when it is converted into the int type. For instance, a bool type containing a value which is either a True or False value, when converted through integer promotion , will have its values changed to “1 for True” and “0 for False”. In this way, the original value is preserved coming into the int type.
Moreover, the integer promotion type conversion is performed on expressions that are:
In short, C++ will always prioritize the int type values in operations that involve calculations. For example, let’s say that we are comparing a char variable f and the value ‘b,’ these values will be first converted to the int type before performing a calculation such as:
c < 'a'
Performing Some of the Usual Arithmetic Type Conversions
In some cases where we end up with operands that differ in arithmetic types even after performing integer promotion type conversion, we will need to perform an implicit type conversion using int type hierarchy . The diagram highlighting this implicit type conversion has already been shown previously, here’s a reminder of what hierarchy type conversion refers to.
When further performing an implicit type conversion using type hierarchy after integer promotion, we mainly convert the two operands into a type that holds the highest position in the hierarchy. When these two implicit type conversions (integer promotion and type hierarchy) are performed, they are collectively known as “Usual Arithmetic Type Conversions.” For example, carefully assess the type conversion shown below:
short size(512); double res, x = 1.5;
res = size / 10 * x;      // short -> int -> double
In this example, we are performing two mathematical operations, i.e., division, and multiplication. The original type of the size variable is short . Before we can perform the division operation in size/10 , the type of the size variable is promoted from short to int . Once we obtain the result of this operation (which would be 50), the type of this result is further converted to the double type by implicit type conversion of hierarchy. Once the type of the result is converted to double , only then can we perform the multiplication operation with x. This is because the value of x is of a double type, and the resulting value of the integer division is of the int type.
Generally, ‘Usual Arithmetic Type Conversions’ are most often sought to be performed on conditional operators such as (?:) and all of the binary operations. However, the only condition specified for the Usual Arithmetic type conversions is that the corresponding operands should be of the Arithmetic type.
Now let’s discuss a few cases where we can apply the Usual Arithmetic Type Conversions.
  1. Converting Signed Integers
As we know that an integer can refer to either a positive number or a negative number, hence we will need to address both states of signed integer conversions. The figure shown below accurately demonstrates and elaborates on the conversion of a positive number.
For the conversion of negative numbers (integers), for instance, -10, we first need to calculate this binary number’s bit pattern and then generate a corresponding binary complement. This process has been demonstrated below:
It is important to remember that the interpretation of the value of a negative number is subject to change if the type is unsigned . In the following sections, we will discuss the varying procedures for type conversions relative to specific types.
  1. Conversion of an Unsigned Type to a Larger Integral Type
For converting an unsigned type to a larger integral type, we will need to perform a process known as ‘Zero extension .’ In zero extension, we take the number that we want to convert and calculate its bit pattern. Once the bit pattern has been calculated, we simply expand the bit pattern to make it match the type in which it is being converted. This is done by simply adding zeroes to the bit pattern from the left-hand side. In other words, the zero extension process involves the expansion of a number’s bit pattern by adding zeroes to it, to make its size match the destination type. For example,
unsigned char to int or unsigned int
  1. Conversion of a Signed Type to a Larger Integral Type
First, we will talk about the case where the new type is also signed . To represent signed integers, we need to generate a binary complement of the corresponding numbers. To preserve the original value of the numbers in such cases, we need to perform a process known as ‘Sign extension.’ In the sign extension process, we expand the integer’s original bit pattern by padding the sign bit from the left direction. For example
char to int, short to long
In this way, the bit pattern is expanded to match the length of the destination type. This process has also been depicted in the figures shown previously.
Now let’s talk about the scenario where the new type is unsigned . When dealing with such integers, the original value of the negative number cannot be preserved or retained. However, if the type in which we want to convert the number has a bit pattern that is of the same length, then the bit pattern is retained as there is no need to perform bit pattern extensions. But even if the bit pattern is retained, this does not mean that it will be interpreted the same way. In such cases, the sign bit will become insignificant, i.e., it will lose its significance. So, if the type convert destination is longer than the original type, then a sign extension is performed to generate a new bit pattern, and this bit pattern is then interpreted as unsigned . For example
char to unsigned int, long to unsigned long
  1. Conversion of an Integral Type to a Floating-Point Type
In this type of conversion, the original value of the number is preserved while being converted into a floating-point number featuring an exponent. However, there are cases where the original number may be rounded-off during conversion. This is evident when converting a value of the long or unsigned long type to a float type value. For example.
int to double, unsigned long to float
  1. Conversion of a Floating-Point Type to a Bigger Floating-Point Type
The core process remains the same; we are simply converting the float type to a double type or the double type to an even larger type, such as the long double type, as shown below.
float to double, double to long double
Throughout this type of conversion, the original value of the number is retained.
Implicit Type Conversions with Assignment Operators
Even if we are not familiar with assignment operators, everybody has come across these operators when exploring programming. We have even used assignment operators in the C++ programs shown in this book. An assignment operator allows you to assign a value to a variable. There are several shorthands and operands of this tool in programming, but the most common one is the equal sign (=).
In this section, we will be discussing how implicit type conversions can be performed during assignments. You might find it interesting that during assignments, arithmetic types can also be brought into the process. In a sense, the compiler acts as a mediator balancing out the left and right-hand sides of the assignment operands. In other words, the value’s type on the right-hand side of the assignment operator is adjusted to match the value’s type on the left-hand side of the assignment operator.
However, not all assignments are as simple as x = 2. Assignments can also have multiple statements (including many calculation operations, etc.) and a complex structure. Such assignments are termed as ‘compound assignments.’ Hence, in compound assignments, the process is carried out by first dealing with the required calculations using the general arithmetic type conversions. Once that has been dealt with, then the type conversion is performed.
Regardless, there are two particular scenarios out which a programmer will most likely face one during type conversions in assignments. These have been listed below:
  1. In an assignment, if the variable’s type is seen to be higher or bigger than the type of the value to which it is supposed to be assigned, then the value’s type must be promoted to match the variable’s type. For this type of conversion, we follow the rules that are defined in the Usual Arithmetic Types Conversion process. For example :
int i = 100;
long lg = i + 50;     // Result of type int is
// converted to long
  1. In an assignment, if the type of the value is seen to be higher or bigger than the type of the variable to which it is being assigned to, instead, of promoting the variable’s type, we must demote the value’s type accordingly. Depending on individual circumstances, the following procedures cover most of the encounters in this type of conversion during assignments :
  1. For converting an integral type to a smaller type, we must consider the two different cases for signed and unsigned int type conversions. Firstly, for converting an int type to a smaller type, we simply eliminate the most significant byte(s) from the bit pattern. If the resulting type is also unsigned, then the resulting bit pattern will be interpreted as unsigned. If the resulting type is signed, then the bit pattern will be simply interpreted as ‘signed.’ However, the original value of the number will only be retained if the new type is capable of representing it. An example of this process has been shown below :
long lg = 0x654321; short st;
st = lg;                           //0x4321 is assigned to st.
However, if we are converting an unsigned type into a signed type, then the original bit pattern remains the same as before, and this bit pattern is simply interpreted as ‘signed.’ For example
int i = –2; unsigned int ui = 2;
i = i * ui;
// First the value contained in i is converted to
// unsigned int (preserving the bit pattern) and
// multiplied by 2 (overflow!).
// While assigning the bit pattern the result
// is interpreted as an int value again,
// i.e. –4 is stored in i.
  1. For converting a floating-point type to an integral type, we simply need to remove the decimal portion of the floating-point value. For instance, if we want to convert the floating-point number 2.9, then we simply remove the .96 part and round off the original number. Rounding off can be done by simply adding 0.5 to the floating-point number if it is positive and subtracting 0.5 from the floating-point number if it is negative. So, in the case of this example, by removing the decimal portion, we are left with the integer 2. However, after rounding it off (2.9 + 0.5), then the floating-point value is converted to an integer 3. However, the result of this type of conversion can be unpredictable at times, especially when the resulting value of int type is either too large or too small for the type itself. This is evident when converting a negative floating-point type value to an unsigned int type. For example
double db = –4.567;
int i; unsigned int ui;
i = db;               // Assigning –4.
i = db – 0.5;          // Assigning –5.
ui = db;              // –4 is incompatible with ui.
  1. We will now discuss the case where a programmer wants to perform type conversion on a value belonging to the floating-point type into a smaller type. In such a type of conversion, there can be two results. One is that if the value of the floating-point number corresponds to the specified range of this destination type, then the original value of this number will be preserved at the cost of the number’s accuracy. On the other hand, if the floating-point number is outside the range of the destination type (i.e., the value can be too large for the type to be able to represent it), then the end result will be unpredictable. An example of the conversion of a floating-point type to a smaller type has been shown below :
double d = 1.23456789012345;
float f;
f = d;                        // 1.234568 is assigned to f.
Some Other Type Conversions
Until now, we have chiefly discussed implicit type conversions, and while their use is more prevalent for fundamental C++ programming, there are other types of conversions as well that are worth discussing. In this section, we will talk about implicit type conversions in terms of function calls and a new type of conversion, which we haven’t discussed up till now, Explicit type conversion.
Using Implicit Type Conversions in Function Calls
We have already discussed function calls in the 2nd chapter of this book, if there is something that you do not understand in this section about function calls, please refer to the topic that addresses this concept.
Implicit type conversion in a function call actually works pretty similarly to how implicit type conversions in assignments are processed. This is because, in function calls, the arithmetic types of the arguments being passed to the function are converted into the types specified in the function prototype. In this way, the parameters of the prototype function are followed and retained. For example:
void func( short, double);         // Prototype
int size = 1000;
// . . .
func( size, 68);                   // Call
In this example, you can see that the func() function has two arguments whose parameters have already been defined in the function prototype at the beginning. These arguments are supposed to be of short and double types. However, in the actual function call, you can see that the arguments being used are both int types. In such cases, implicit type conversion is performed to convert the int value size to a double type, and the integer 68 is converted to a double type value. Note that when an implicit type conversion takes place for this specific example, the compiler will issue a warning. The main purpose of this warning is to remind the user that since the int type is being converted to a short type, there are chances for data loss. To avoid such warnings, we have the option of performing an explicit type conversion .
Explicit Type Conversion
Just as the name suggests, in this type of conversion, we explicitly convert the expression types by leveraging the functionality of the cast operator (type) . In other words, we use the cast operator to perform an explicit type conversion. The syntax for this has been shown below
(type) expression
According to this syntax, we are directly converting the value assigned to the expression to a specified type. As such, explicit type conversion is also commonly referred to as ‘casting ,’ even the operator is named after this process.
Since the cast operator (type) is, by nature, a unary operator , it holds a higher level of precedence than the traditional arithmetic operators in C++. Let’s understand explicit type conversions with the help of an example.
int a = 1, b = 4;
double x;
x = (double)a/b;
In this example, we can see that the value assigned to the variable ‘a’ has been explicitly converted from an int type to a double type. Explicit type conversion wasn’t performed on the other variable because it’s type would automatically be converted to match the double type by the compiler through implicit type conversion. After the implicit type conversion is performed, the floating-point division operation is then carried out by the compiler, and the exact result obtained is 0.25. This value is finally assigned to the x variable by using the assignment operator. Note that if we did not perform casting in this example, then the program would have performed a simple integer division operation, and the result it would have given would be 0, which is obviously incorrect.
The cast operator discussed in this section for explicit type conversion is for general purposes. In C++, there are other operators (for example, dynamic_cast<>) available as well that can be used for explicit type conversion, but those are used for specific cases only.