Promotion and Conversion
If you add a
short intto along intdo 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
intadded to anintmakes anint - A
long doubleadded to along doublemakes 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 longs 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,
usPopulationandusAreaInSqMilesare bothlongs, 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
.0after the numbers in the constants?
To make it clear that the constant is a
doubles, rathwerints.
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 ints 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
ints or with twodoubles.
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→doubleis still a conversion). - Changing to a smaller incarnation of the basic type is always a conversion (e.g.,
int→short intordouble→float). - Signed→unsigned is always a conversion (e.g.,
short int→unsigned long intis 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 - bandais a signedintandbis 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 longs 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 doubles.
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 adoubleon 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 ints use at least as many bits as ints. |
| Bigger to smaller type of the same kind | int to char |
Conversion | long ints use at least as many bits as ints. |
| 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.)