After conquering the vagaries of quantum mechanics, physicists turned their attention to completely deciphering the behavior of mathematical operations in C++ programs. The years-long effort resulted in today's announcement from the C++ Elucidation Research Network (CERN) that they have finally broken the code.
Though computers are able to accomplish a tremendous amount of work in a short amount of time and regularly put their human computing counterparts to shame (after all, who can multiply 549872 times 86384572 in their head faster than a computer?), in some ways humans remain superior.
For instance, consider the mathematical equation
The answer is
Let's see what that too-cool-for-school computer says:
#include <iostream>
int main() {
int five{5};
int two{2};
std::cout << "5/2: " << (five / two) << "\n";
return 0;
}
will print
5/2: 2
Uhm, what?
Just when you thought that you could get away from clutches of types in programming, they pull you right back in! The answer to this mystery lies in types.
Remember that expressions are anything that has a value. More importantly (for this discussion), all values in C++ have a type! Never forget the definition of type: A range of valid values and the valid operations that you can perform on those values.
Put that knowledge together and you can reason that ... (five/two)
is an expression, it generates a value and, therefore, that value has a type. C++ determines the type of a value based on the composition of the expression that generates it! It just so happens that C++ specifies that when you have an operator (remember the definition of operator?) whose operands are int
s, the type of the value when that expression is evaluated is also an int
.
Ah, so that's the rub!
Dividing two int
s always yields an int
and an int
cannot hold numbers with decimal points! So, when a C++ program generates a value for an expression whose type is int
but the value has numbers after the decimal point, what does it do? Does it round the number up? Nope! It simply lopes off that extra information in a process called truncation.
So, how can you get around this obnoxious behavior and teach the computer to give the answer that you really want?
C++ cannot evaluate an expression when there the two operands of an operator have different types. So, when C++ sees an operator whose operands have different types, it goes through a process of attempting to coerce the two operands to the same type!
The rules and mechanics of this process are fairly complex (and it is best to consult a reference any time you rely on that behavior), but there is one that is easy to remember and very handy:
When C++ sees an operator whose operands are an
int
and adouble
, the operator with theint
operand is "upgraded" to adouble
.
With that upgrade, the two operands are both double
s and as a result (obviously) the, er, result of that operation also has the type double
. Exactly what we want.
So, all that's left is to convince C++ that either five
or two
is really a double. Because whole numbers (int
s) are a subset of all numbers, we can assure C++ that it is okay to think about either five
or two
as a double
without losing any information. In other words, if C++ considers either five
or two
as a double, none of the meaning of
N.B.: This is not always 100% guaranteed to be the case. There is the possibility that you could store a very, very large whole number and fail to be able to convert it to a double-precision number. But, most of the time it works just fine!
N.B. (2): This assumption does not hold in the opposite direction -- if you think about int
, it will lose some of its essence. You will only be able to think about
Okay, now for the arm twisting: C++ gives us an awesome tool known as the static_cast
that we can use to assure the compiler that we can treat of a variable of one type as a variable of a different type. Of course there are rules about when it is safe to do this type of operation -- we don't want to be able to tell C++ that a std::string
is a bool
.
#include <iostream>
int main() {
int five{5};
int two{2};
std::cout << "5/2: " << (static_cast<double>(five) / two) << "\n";
return 0;
}
will print
5/2: 2.5
Yes, problem solved!
In general, the syntax for a static_cast
looks like
static_cast<new_type>(expression)
when you want to tell C++ to interpret the type of expression
as new type
. Note how we used the word interpret. A static_cast
is itself an expression -- in other words, it has a type and a value. The type of the value of that expression is new_type
and the value is the value of expression
as that type. A static_cast
does not change anything about the value or type of expression
.
One final important caveat about static_cast
. The expression
is evaluated before the static_cast
occurs. Why is that important?
What is the output of the following program?
#include <iostream>
int main() {
int five{5};
int two{2};
std::cout << "5/2: " << static_cast<double>(five / two) << "\n";
return 0;
}
5/2: 2
What? Think about it! The first thing that C++ does is evaluate the expression 5/2
. Those are two int
s and, well, we are right back where we started. The value of that expression is int
! It is only then that the static_cast
does its job and tells C++ to interpret the int
double
.
Be very careful!