Promotion and Conversion
If you add a
short int
to along int
do you get a regularint
?…No. But we do need to talk about these kinds of questions!
When Everything is the Same Type
Everything is easiest when we're working with the same type, so…
- An
int
added to anint
makes anint
- A
long double
added to along double
makes along double
.
So if we say
long usPopulation = 336858333;
long usAreaInSqMiles = 3718712;
std::cout << "People per square mile = "
<< usPopulation/usAreaInSqMiles
<< std::endl;
the calculation usPopulation/usAreaInSqMiles
takes in two long
s and the result is a long
. So it will print
People per square mile = 90
Hey! That's not right! 336858333/3718712 is 90.584679050165761!
And it should at least round it properly to 91 if it's going to make it be a
long
!Gah!
Okay, so this is a good point and it's why we're covering this topic. It's one of the most common errors people make when they're first learning C++.
We're following exactly the rule we gave earlier,
usPopulation
andusAreaInSqMiles
are bothlong
s, so C++ useslong
's division operator, which does integer division, and truncates the result to an integer rather than rounding it.
If, however, we'd instead said
double usPopulation = 336858333.0;
double usAreaInSqMiles = 3718712.0;
std::cout << "People per square mile = "
<< usPopulation/usAreaInSqMiles
<< std::endl;
it would have used double
's division operator and produced the output:
People per square mile = 90.5847
Dare I ask why we have to put
.0
after the numbers in the constants?To make it clear that the constant is a
double
s, rathwerint
s.And why doesn't the result have MORE digits after the decimal point? It doesn't seem very precise for a
double
!That's just the way it prints numbers by default, there are more digits, they're just not shown when printing. There's a way to change it, but we'll get to that later.
Promotion: Different Sizes of the Same Type
Let's assume that long
is a bigger type than int
(specifically, that we're using the LP64 memory model where int is 32 bits and long is 64 bits.).
If we do something like:
long usPopulation = 336858333;
int thisWeeksGain = 36538;
std::cout << "US population is now "
<< usPopulation + thisWeeksGain
<< std::endl;
C++ does the sensible thing, it takes the int
holding thisWeeksGain
and widens it to be a long
before it adds it to usPopulation
, so the final result we're printing is a long
. This widening process is called a promotion.
It would also be considered a promotion if we had declared thisWeeksGain
as an unsigned int
, so long as it's smaller than a long
. This is because the range of an unsigned int
is [0…4,294,967,297] and the range of a long
is [−9,223,372,036,854,775,808…9,223,372,036,854,775,807], so the range of an unsigned int
falls entirely inside the range of a long
.
C++ performs promotions automatically, and its rule for promotion is
- The types must be the same kind (integer to integer or floating point to floating point).
- The range of the type being promoted to must completely include the range of the type being promoted from.
Thus, a promotion cannot ever cause information to be lost.
Any other kind of conversion between numeric types is a conversion.
We can also get C++ to perform promotions when we initialize new variables.
short weekDay = 3;
int yearStart = weekDay;
Explicit Promotions
We can also perform promotions explicitly. For example,
signed char dollar = 36;
std::cout << "Printed as a character " << dollar << std::endl
<< "Printed as an integer " << int(dollar) << std::endl;
prints
Printed as a character $
Printed as an integer 36
We use the exact same syntax for conversions as well.
int(3.1415)
is3
.And conversions are our next topic!
Deeper Dive: Auto-Promotion of Small Integers
Integer types smaller than an int
(e.g., short int
and signed char
) always promote to an int
when we use them in arithmetic. Somewhat strangely, small unsigned int
s promote to int
not unsigned int
.
Conversion: Changing Between Types
If we move a numeric value between types and it isn't a promotion, it's a conversion.
Suppose instead we had written
double usPopulation = 336858333;
int thisWeeksGain = 36538;
std::cout << "US population is now "
<< usPopulation + thisWeeksGain
<< std::endl;
Here C++ would have to make a choice for what type the result of usPopulation + thisWeeksGain
should be. Given a choice between an int
and a double
for the result of the addition, it converts the type to a double
.
Notice that C++ didn't ask us if it's okay to perform the conversion.
It'd be kinda weird if it stopped and asked us questions while compiling our code!
True.
But why can't it compile the code without doing a conversion?
The only kind of arithmetic operators that exist for numbers are ones where both the lefthand side and righthand side are the same type, so it needed to do addition with two
int
s or with twodouble
s.
Conversions can potentially lose information!
If we run this code
long worldPop = 7960583250;
std::cout << "World pop as long " << worldPop << std::endl
<< "----------> float " << float(worldPop) << std::endl
<< "-----------> long " << long(float(worldPop)) << std::endl;
it prints
World pop as long 7960583250
----------> float 7.96058e+09
-----------> long 7960583168
Oh dear, we lost 82 people!
Some rules:
- Conversions (and promotions) often happen automatically, particularly when we call a function that wants a different type from the one we're providing but we can convert our value to that type.
- Changing any kind of integer to any kind of float (or the other way) is always considered a conversion (even if on a particular machine no information could be lost for those particular sizes, so
char
→double
is still a conversion). - Changing to a smaller incarnation of the basic type is always a conversion (e.g.,
int
→short int
ordouble
→float
). - Signed→unsigned is always a conversion (e.g.,
short int
→unsigned long int
is a conversion). - Unsigned→signed is usually a conversion (unless the signed type is larger, which it usually isn't).
What happens if I do
a - b
anda
is a signedint
andb
is anunsigned int
?Good question! The result is an
unsigned int
. This can be very surprising if you were expecting to get a negative number!
Conversions and Promotions Aren't Done “Early”
While conversions and promotions happen automatically, they happen no earlier than needed. So if we say:
long usPopulation = 336858333;
long usAreaInSqMiles = 3718712;
double popPerSqMile = usPopulation/usAreaInSqMiles;
It will first perform division on long
s to produce a long
result and only afterwards will it convert it to a double
. To get a double
as the result
from division, we need to be dividing double
s.
That might easily catch me out, I think.
It catches everyone out at least some of the time.
Sorry.
That's okay, Bjarne Stroustrup, creator of C++. The other way would have caused problems too!
coughcoughthisiswhyPythonhastwodivisionoperatorscoughcough
So we need to write:
long usPopulation = 336858333;
long usAreaInSqMiles = 3718712;
double popPerSqMile = double(usPopulation) / double(usAreaInSqMiles);
Actually, you only need one of those
double(…)
conversion operators, because once we have adouble
on one side, it'll auto convert the other side to adouble
.
Summary: Promotion and Conversion
Here's a summary table with examples of the different types of conversion and promotion we've seen!
Category | Example(s) | Promotion or Conversion? | Why? |
---|---|---|---|
Smaller to bigger type of the same kind | int to long int |
Promotion | long int s use at least as many bits as int s. |
Bigger to smaller type of the same kind | int to char |
Conversion | long int s use at least as many bits as int s. |
Signed/unsigned integer types | int to unsigned int |
||
unsigned int to int |
Conversion | the ranges of signed and unsigned types are different | |
Different kinds of numbers (int vs. float ) |
int to float |
||
float to int |
Conversion | integers and floating point numbers are different |
(When logged in, completion status appears here.)