Previously, you learned that a value of a variable is stored as a sequence of bits, and the data type of the variable tells the compiler how to interpret those bits into meaningful values. Different data types may represent the “same” number differently -- for example, the integer value 3 and the float value 3.0 are stored as completely different binary patterns.
So what happens when we do something like this?
1 |
float f{ 3 }; // initialize floating point variable with int 3 |
In such a case, the compiler can’t just copy the bits representing the int
value 3
into float
variable f
. Instead, it needs to convert the integer value 3
to a floating point number, which can then be assigned to float
variable f
.
The process of converting a value from one data type to another is called a type conversion. Type conversions
can happen in many different cases:
- When assigning to or initializing a variable with a value of a different data type:
1 2 |
double d{ 3 }; // initialize double variable with integer value 3 d = 6; // assign double variable the integer value 6 |
- When passing a value to a function where the function parameter is of a different data type:
1 2 3 4 5 |
void doSomething(long l) { } doSomething(3); // pass integer value 3 to a function expecting a long parameter |
- When returning a value from a function where the function return type is of a different data type:
1 2 3 4 |
float doSomething() { return 3.0; // Return double value 3.0 back to caller through float return type } |
- Using a binary operator with operands of different types:
1 |
double division{ 4.0 / 3 }; // division with a double and an integer |
In all of these cases (and quite a few others), C++ will use type conversion
to convert one data type to another data type.
There are two basic types of type conversion
: implicit type conversion
, where the compiler automatically transforms one data type into another, and explicit type conversion
, where the developer uses a casting operator to direct the conversion.
We’ll cover implicit type conversion
in this lesson, and explicit type conversion
in the next.
Implicit type conversion
Implicit type conversion (also called automatic type conversion or coercion) is performed whenever one data type is expected, but a different data type is supplied. If the compiler can figure out how to do the conversion between the two types, it will. If it doesn’t know how, then it will fail with a compile error.
All of the above examples are cases where implicit type conversion
will be used.
There are two basic types of implicit type conversion
: promotions
and conversions
.
Numeric promotion
Whenever a value from one fundamental data type is converted into a value of a larger fundamental data type from the same family, this is called a numeric promotion (or widening, though this term is usually reserved for integers). For example, an int
can be widened into a long
, or a float
promoted into a double
:
1 2 |
long l{ 64 }; // widen the integer 64 into a long double d{ 0.12f }; // promote the float 0.12 into a double |
While the term numeric promotion
covers any type of promotion, there are two other terms with specific meanings in C++:
- Integral promotion involves the conversion of integer types narrower than
int
(which includesbool
,char
,unsigned char
,signed char
,unsigned short
, andsigned short
) to anint
(if possible) or anunsigned int
(otherwise). - Floating point promotion involves the conversion of a
float
to adouble
.
Integral promotion
and floating point promotion
are used in specific cases to convert smaller data types to int
/unsigned int
or double
, because int
and double
are generally the most performant types to perform operations on.
The important thing to remember about promotions is that they are always safe, and no data loss will result.
For advanced readers
Under the hood, promotions generally involve extending the binary representation of a number (e.g. for integers, adding leading 0s).
Numeric conversions
When we convert a value from a larger type to a similar smaller type, or between different types, this is called a numeric conversion. For example:
1 2 |
double d{ 3 }; // convert integer 3 to a double (between different types) short s{ 2 }; // convert integer 2 to a short (from larger to smaller type within same type family) |
Unlike promotions, which are always safe, conversions may or may not result in a loss of data. Because of this, code that causes an implicit conversion to be performed will often cause the compiler to issue a warning.
The rules for conversions are complicated and numerous, so we’ll just cover the common cases here.
In all cases, converting a value into a type that doesn’t have a large enough range to support the value will lead to unexpected results. For example:
1 2 3 4 5 6 7 8 9 |
int main() { int i{ 30000 }; char c = i; // chars have range -128 to 127 std::cout << static_cast<int>(c); return 0; } |
In this example, we’ve assigned a large integer to a char (that has range -128 to 127). This causes the char to overflow, and produces an unexpected result:
48
However, converting from a larger integral or floating point type to a smaller similar type will generally work so long as the value fits in the range of the smaller type. For example:
1 2 3 4 5 6 7 |
int i{ 2 }; short s = i; // convert from int to short std::cout << s << '\n'; double d{ 0.1234 }; float f = d; std::cout << f << '\n'; |
This produces the expected result:
2 0.1234
In the case of floating point values, some rounding may occur due to a loss of precision in the smaller type. For example:
1 2 |
float f = 0.123456789; // double value 0.123456789 has 9 significant digits, but float can only support about 7 std::cout << std::setprecision(9) << f << '\n'; // std::setprecision defined in iomanip header |
In this case, we see a loss of precision because the float
can’t hold as much precision as a double
:
0.123456791
Converting from an integer to a floating point number generally works as long as the value fits within the range of the floating type. For example:
1 2 3 |
int i{ 10 }; float f = i; std::cout << f; |
This produces the expected result:
10
Converting from a floating point to an integer works as long as the value fits within the range of the integer, but any fractional values are lost. For example:
1 2 |
int i = 3.5; std::cout << i << '\n'; |
In this example, the fractional value (.5) is lost, leaving the following result:
3
Conversions that could cause loss of information, e.g. floating point to integer, are called narrowing conversions. Since information loss is generally undesirable, brace initialization doesn’t allow narrowing conversions.
1 2 |
double d{ 10.0 }; int i{ d }; // Error: A double can store values that don't fit into an int |
Evaluating arithmetic expressions
When evaluating expressions, the compiler breaks each expression down into individual subexpressions. The arithmetic operators require their operands to be of the same type. To ensure this, the compiler uses the following rules:
- If an operand is an integer that is narrower than an int, it undergoes integral promotion (as described above) to int or unsigned int.
- If the operands still do not match, then the compiler finds the highest priority operand and implicitly converts the other operand to match.
The priority of operands is as follows:
- long double (highest)
- double
- float
- unsigned long long
- long long
- unsigned long
- long
- unsigned int
- int (lowest)
We can see the usual arithmetic conversion take place via use of the typeid
operator (included in the <typeinfo> header), which can be used to show the resulting type of an expression.
In the following example, we add two shorts:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> #include <typeinfo> // for typeid() int main() { short a{ 4 }; short b{ 5 }; std::cout << typeid(a + b).name() << ' ' << a + b << '\n'; // show us the type of a + b return 0; } |
Because shorts
are integers, they undergo integral promotion
to ints
before being added. The result of adding two ints
is an int
, as you would expect:
int 9
Note: Your compiler may display something slightly different, as the format of typeid.name() is left up to the compiler.
Let’s take a look at another case:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> #include <typeinfo> // for typeid() int main() { double d{ 4.0 }; short s{ 2 }; std::cout << typeid(d + s).name() << ' ' << d + s << '\n'; // show us the type of d + s return 0; } |
In this case, the short
undergoes integral promotion
to an int
. However, the int
and double
still do not match. Since double
is higher on the hierarchy of types, the integer 2
gets converted to the double
2.0
, and the doubles
are added to produce a double
result.
double 6.0
This hierarchy can cause some interesting issues. For example, take a look at the following code:
1 |
std::cout << 5u - 10; // 5u means treat 5 as an unsigned integer |
you might expect the expression 5u - 10
to evaluate to -5 since 5 - 10 = -5. But here’s what actually happens:
4294967291
In this case, the signed integer
10
is promoted to an unsigned integer
(which has higher priority), and the expression is evaluated as an unsigned int
. Since -5
can’t be stored in an unsigned int
, the calculation wraps around, and we get an answer we don’t expect.
This is one of many good reasons to avoid unsigned integers
in general.
Quiz time
Question #1
What’s the difference between a numeric promotion and a numeric conversion?
![]() |
![]() |
![]() |
Hi Alex, you wrote this example but in the comment I think you meant "return" instead of pass.
Thanks for pointing out the error. Fixed!
Why overflow occurred when integer is promoted to unsigned integer?
Because 5 - 10 only equals -5 with signed numbers. Unsigned numbers can't be negative, so the result overflows to a huge positive number.
dear alex,
I created a program regarding to what you taught.
here is the program.
#include<iostream>
#include<typeinfo>
using namespace std;
int main()
{
unsigned long long a=1.2;
int b=4;
cout<<typeid(a/b).name()<<"\t"<<a/b;
}
However the output was
y 0.
I would want to know what does the "y" stands for.
thank you.
Probably long long, but the results of typeid.name() are compiled dependent so you'd have to check your compiler documentation, or determine by experimentation.
Ok, thank you alex.
I'll look to it.
Greetings, Alex! I'm sure I am not the only one with this confusion. I seem to not be understanding the precise difference between float and double in terms of variable assignment. in your example, is 6.0 called a double for the sake of the example (hypothetically if the programmer wanted more digit precision than float) or are there cases of two or three sig digs that would be considered a double?
As you're aware, float and double are both floating point types, with double offering more precision than float, but using more memory.
Variables are of whatever type they're defined as. Floating point literals are treated as floats if they have an f suffix, and as doubles if they have no suffix.
6.0 is a double because d is a double, and s is a short, and when you add a double and a short, the result is a double. Precision doesn't really come into play at all here.
I'm not sure if I actually answered your question.
Yes you did! Thank you much
Just to clarify, if I was to compile:
float a(9.0);
a would be a double and not a float because it does not contain the 'f' suffix?
If this is the case, what is the purpose? If I wanted a to be a double, I would have declared it using the 'double' keyword.
No. a is always a float. It's the 9.0 that's a double in this case because it's lacking the f suffix.
Hello, alex, may i ask a question:
static_cast<int>(c);
is this a function to converting the data type? why do we need to use <int> instead of something like this static_cast(int,c)? thanks
This converts the _value_ of object c into an integer (the type of object c itself remains unchanged).
The <int> specifies the target type to convert to (in this case, an integer), and the object in parenthesis is the expression that gets evaluated to produce a single value to convert. In this case, that's just the variable c being evaluated for its value.
Good afternoon!!
I have a question that I would like to ask:
I heard about Project Euler and I would like to know if it is a great way to strengthen my skills in c++ or also what can I do every day in my life to use and strengthen my c++ skills?
Thank you for the attention
I'm not familiar with Project Euler, so I couldn't say. Try it and see if you find it valuable for learning.
Hi Alex,
In the following example, we add two shorts:
#include <iostream>
#include <typeinfo> // for typeid()
int main()
{
short a(4);
short b(5);
std::cout << typeid(a + b).name() << " " << a + b << std::endl; // show us the type of a + b
return 0;
}
Because shorts are integers, they undergo integral promotion to ints before being added. The result of adding two ints is an int, as you would expect:
int 9.
The size of short is 2 bytes (range: -32,768 to 32,767), this means 4 & 5 should be ok. I don't understand why they had to undergo integral promotion to ints before being added?
Thanks.
Hi Alex,
Please help!
Example 1:
int i = 3.5; // initialize an integer type with double value.
std::cout << i;
In this example, the fractional value (.5) is lost, leaving the following result:
3
It really makes sense because I've learned that an integer type variable is a variable that can only hold non-fractional numbers (e.g. -2, -1, 0, 1, 2).
=============================================
Example 2:
float i = 3; // initialize floating point type with integer value
std::cout << i;
The result is:
3
I don't understand why the result was not 3.0 because I've learned that a floating point type variable is a variable that can hold a real number, such as 4320.0, -3.33, or 0.01226. (it looks to me a real number is a number with a decimal point).
Thanks, Have a great day.
In example 2, your float variable i is holding the value 3.0, but std::cout prints 3 by default.
If you want to print one decimal, you can do so as follows:
Can you explain how signed int is converted to unsigned so it causes overflow?
This program illustrates it:
196 is 0x11000100 in binary. When we assign unsigned u to signed s, that 0x11000100 gets interpreted as a 2's complement number instead of a normal binary number. 0x11000100 = -60 in 2's complement.
You'll see that this program prints -60.
"If an operand is an integer, it undergoes integral promotion (as described above)."
This is a bit confusing because as I get it even if the both operands are int they undergo an integral promotion. If this is not correct you should write short (int) instead of integer (before the comma).
Updated wording to: If an operand is an integer that is narrower than an int, it undergoes integral promotion (as described above) to int or unsigned int.
In the second to last example, I was a little confused as to why the the two short integers got promoted to regular integer types during the arithmetic operation. To me, the wording right before the example (under "Evaluating arithmetic expressions"), made it seem as though an operand would only get promoted in this situation if the operands were of different types. As you wrote: "If operands of mixed types are used, the compiler will implicitly convert one operand to agree with the other using a process called usual arithmetic conversion."
Maybe it should be re-worded to make it a little more clear for slow minds like mine :)
I agree, the wording was confusing. I've updated the lesson text to better indicate that integers undergo promotion even if they match.
Love you Alex!
Hi Alex,
For the below code using typeid(), I got different result than the mentioned one.
mentioned output is double 6.0, but mine is d 6. Why there is a difference?
#include <iostream>
#include <typeinfo> // for typeid()
int main()
{
double d(4.0);
short s(2);
std::cout << typeid(d + s).name() << " " << d + s << std::endl; // show us the type of d + s
return 0;
}
Compilers are free to print the result in whatever format they want in response to typeid().name() call.
Alex,
In the beginning of the chapter, discussing the first case where implicit type conversion can happen, you wrote:
" Assigning to or initializing a variable with a value of a different data type: "
Even though you are talking about assigning and initialising, both statements in the code are initialisations - implicit and explicit ones. This confused me, that I had to review the prior chapter about the topic to be sure. So, won't this:
be more appropriate to the description?
Yes, sloppy wording on my part. It's fixed now.
implicit conversion
int x=5+3.55//5 is int and 3.55 float//5 converted in float but what is the type of int x.is it int or float??
x is defined as an int, so it stays an int.
By the way, 3.55 is a double, not a float.
So this statement is the equivalent of int x = 8.55, which causes 8.55 to be truncated to an integer and assigned to x.
Alex,
In this lesson you introduced typeid and .name. Can you please give us an explanation on how to use typeid and .name and the proper syntax?
I would have thought they would have used typeof since they have sizeof as a keyword to make things easy to remember.
Alex,
So what your telling us, from your last example is, the compiler is not smart enough to convert a signed integer into and unsigned one, is that right? Is this also true for signed and unsigned floats?
Can you fool the compiler with this 5u - (10) or maybe this 5u - (10u)?
No. The compiler can convert signed integers into unsigned integers. The problem here is that because the subtraction expression gets evaluated as the subtraction of two unsigned values, 5u - 10u evaluates to 4294967291 instead of -5 due to overflow.
If you want this to work correctly, you'd need to use a static_cast to convert the unsigned number (5) to a signed number so the subtraction evaluated as two signed numbers.
There's no such thing as a signed or unsigned float. Floating point numbers are always signed.
I think you should write a bit more about typeid. What I discovered is that typeid can't be used without header typeinfo. Include this in the tutorial if I m ri8.
A question:
I m using Code::Blocks. When I use short and int instead of long long and unsigned int in the above program, I got "39i" as result. I think Code::Blocks outputs character "i" to express an integer calculation (f for floating point). But when I compile and run the above code without replacing long long with short and unsigned int with int, it gives "39x". What is x? Please clear.
Would be great if you can add some tutorials on template metaprogramming.
Thnx Alex :-)
Sorry, outputs are 'i' (not 39i) and 'x' (not 39x). 'i' for short l + int u and 'x' for long long l + unsigned int u. Please see my previous comment to understand everything. I m still confused what does 'x' means in case of long long and unsigned int calculation.
Updated the lesson to include <typeinfo>.
What typeid() returns is compiler specific. Visual Studio returns "nice" type names, like "int". It looks like Code::Blocks returns mangled names. I presume Code::Blocks is using x to represent a 64-bit integer.
Alex,
FYI I'm using the newer version of Code::Blocks vs 16.01, on my Intel MB w/i7 chip, Windows 10-64bit.
It output i 9 for integers when adding the two shorts and d 6 for double when adding the double and short in the two lesson programs above.
So it is working okay, but names would have been nicer.
I don't understand what your saying in your presumption- using x to represent 64-bit integer
Code::Blocks users can upgrade to this version from Sourceforge website. Code::Blocks is not a compiler.
The names displayed by typeid.name() are determined by the compiler. Visual Studio displays nice names (e.g. double), the compiler used by Code::Blocks displays short names (e.g. d).
For 64-bit integers, Visual Studio shows __int64, whereas the compiler used by Code::Blocks shows "x" as the short name. It's just a different way of indicating what type the variable is.
Dear Alex,
According to your website ,
C++ has the following data types along with their format specifier:
Int ("%d"): 32 Bit integer
Long (%ld): 32 bit integer (same as Int for modern systems)
Long Long ("%lld"): 64 bit integer
Char ("%c"): Character type
Float ("%f"): 32 bit real value
Double ("%lf"): 64 bit real value
Can you please look into this code
if the sample input is 3 444 12345678912345 a 334.23 14049.30493
then sample expected output should be
3
444
12345678912345
a
334.23
14049.30493
The code mentioned produces only precision of 6 digits in double i.e.a6.
Why ?
Please help me out.
Thank you. :)
std::cout only prints floats and doubles to a precision of 6 digits by default. This is discussed in lesson 2.5 -- Floating point numbers.
"Numeric promotion...
"
Is the 'f' a typo, or what is its significance here?
The f suffix means 0.12 is a floating point value. Without it, 0.12 would default to double precision.
Typos.
"Instead, it need (needs) to convert the integer 3"
"Convering (Converting) between an integer and a floating point"
Also, when discussing conversions between integers & floating points (you talk about converting in both directions), it may be more helpful to say, "Converting from ____ to ____ ..." instead of "Converting between ____ and ____ ..." so that the reader knows the direction of the conversion.
Updated. Thanks!
Thanks for all these great examples and the helpful summary. Next time someone asks me why type safety matters I'm going to send them here.
"Interesting" is one way to describe it....
I would like to point out that "int nValue = 10 * 2.7" does not really work "as expected". It is not possible to store the value 2.7 in a double. Instead, the value stored in the machine register is something like 2.7000000000000001776... (continuing to 51 digits). You are lucky that the IEEE rounding rules allow the computation to give 27 at the end, but in general you should never expect a floating-point computation on inexact values to give an exact result.
The value 2.5 can be represented exactly as a double, however. So you are guaranteed that 10 * 2.5 will be 25.
Thank you! I read about this in a book a while back and just didn't get the point. Now I do!
Your real world examples and "new programmers tend to do this" advice make this a fantastic tutorial.
x = static_cast(stuff);
the type inside is what you want it to be converted TO?
the type inside is what you want it to be converted TO?
(sry for reposting but the brackets didnt show)
Yes. The type can be int, float, char, double..etc
Sorry Alex, output is 'i' and 'x' only (not 39i and 39x). But I m still wondering, why 'x' in case of long long and unsigned int calculation.
After doing:
It printed out 0.
After doing:
It printed out 48.
So what does the number 48 convert to as a character? Why did it come out as 0? Is that the character it was assigned?
Simply put yes a decimal of 48 converts to a binary of 0011 0000 which is = to a hex value of 30 which evaluates to 0 as a char. Incrementing nValue7 to 49 would return 1 and 50 would return 2 and so on. Remember that an int number and a char number are not necessarily the same binary value.
Your first example prints the ASCII character assigned to code point 48, which is the character '0'. Your second example printed the char as an integer, which prints the numeric value 48.
I am not entirely clear on the second-last example of this page: About half-way through the tutorial, it is explained that since double has higher precedence then int, an arithmetic operation on an int and a double will cause the int to be turned into a double. Yet, here (the second last example), it is stated that the compiler will complain about a double being converted to an int!
Is there thus a difference between first EXPLICITLY declaring a variable of a certain type and then using that variable in an arithmetic operation with an implicitly defined variable (such as 2.5), as opposed to have two implicitly declared and defined variables in an arithmetic operation (such as 2 / 2.5)?
when promoting an int to a double there is no warning because the int is increasing in precedence (changing a 2 to a 2.0 does not cause any unwanted behavior).
The compiler will warn you about lowering the precedence of a value, which happens when you convert a double to an int. (Chaning 2.5 into an int results in 2, so the .5 gets lost)
i have a problem i want to do get input from user then i want to do the multiplication,division,subtraction,addititon,and modulus by using type double.How i will do modulus with double variable any one can provide me the code
Modulus is an integer operation and can not be used with doubles.
That is not really true. The % operator cannot be used with doubles, but <cmath> provides double fmod(double x, double y) which is modulus for doubles.
When I build and compile the code below, the output is -5. You mention that:
"This hierarchy can cause some interesting issues. For example, you might expect the expression 5u - 10 to evalute to -5 (5u means 5 as an unsigned integer). But in this case, the signed integer (10) is promoted to an unsigned integer, and the result of this expression is the unsigned integer 4294967291!"
Is the hierarchy issue compiler specific? I am using VC 2005 Express.
The issue is in how you've written your program. This is a tricky one.
For example, if you do this:
You get 4294967291.
If you do this:
You get -5.
So what's the difference? The answer has to do with the way types are promoted. When the compiler encounters 5 - 10u, it promotes 5 to an unsigned value, and 5u - 10u produces the result 4294967291 (unsigned). However, it's worth noting that 4294967291u and -5 have the exact same bit pattern -- the interpretation depends entirely on whether the value is treated as signed or unsigned.
Because 5 - 10u produces an unsigned value, cout treats it as an unsigned value, and prints 4294967291 as the result.
However, in your case, you've assigned this unsigned value back to a signed integer. Because x is signed, when you print x in the next statement, cout prints the value as if it were signed, which is why it prints -5.
So the ultimate answer is that the statement as written is true. You've simply cast 4294967291 unsigned back to a signed integer and printed that value, which is the value you were intuitively expecting anyway. :)
so in his case, two wrongs made a right?