Converting Between Numeric Types
Convert between integer and floating-point types while avoiding data loss.
What Are Numeric Conversions?
A numeric conversion is a conversion between fundamental numeric types that the promotion rules do not cover. The previous lesson dealt with numeric promotions, which widen a narrow type to int or double and always keep the value intact. Numeric conversions are everything else, and they come with no such guarantee: depending on the types involved, the value may change or information may be thrown away.
The two categories do not overlap. If a conversion is covered by the promotion rules, it is a promotion, not a numeric conversion.
The Five Groups
Numeric conversions fall into five groups:
| Group | Example |
|---|---|
| Integral to a different integral type | short quantity = 3; |
| Floating point to a different floating point type | float price = 3.0; |
| Floating point to integral | int result = 12.75; |
| Integral to floating point | double percentage = 3; |
Integral or floating point to bool |
bool flag = 3; |
Promotions are excluded from the first two groups, since a widening to int or double is a promotion by definition.
This lesson uses copy initialization rather than braces. Brace initialization deliberately rejects some of these conversions, which is the subject of the next lesson, so copy initialization keeps the examples focused on what the conversion itself does.
Three Degrees of Safety
Promotions are always value-preserving. Conversions are not, and an unsafe conversion is one where at least one value of the source type has no equal in the destination type. Sorting them by how much damage they can do gives three categories.
| Category | Value can change | Information lost | Round trip recovers the original |
|---|---|---|---|
| Value-preserving | No | No | Yes |
| Reinterpretive | Yes | No | Yes |
| Lossy | Yes | Yes | No |
Value-Preserving
The destination type can represent every value the source type can hold, so nothing can go wrong. int to long and short to double are typical examples, and compilers stay silent about them.
Because nothing is lost, converting back always reproduces the original:
#include <iostream>
int main()
{
int original{ static_cast<int>(static_cast<long>(3)) };
char letter{ static_cast<char>(static_cast<double>('c')) };
std::cout << original << '\n';
std::cout << letter << '\n';
return 0;
}
Output:
3
c
Reinterpretive
The bits survive but the interpretation changes. Signed to unsigned conversions are the main example. A positive value converts to the same number, but a negative one has no unsigned equivalent, so the result wraps modulo the type's range into a large positive value:
int posValue{ 5 };
unsigned int uPosValue{ posValue }; // 5, value preserved
int negValue{ -5 };
unsigned int uNegValue{ negValue }; // a large positive number instead
No information is destroyed, though, which is what separates this category from the next. Convert back and the original returns:
#include <iostream>
int main()
{
int recovered{ static_cast<int>(static_cast<unsigned int>(-5)) };
std::cout << recovered << '\n';
return 0;
}
Output:
-5
Most compilers leave implicit signed/unsigned conversion warnings switched off. Modern C++ makes these conversions hard to avoid, particularly around standard library container sizes, and the overwhelming majority of them are harmless, so enabling the warning tends to bury real problems under noise. The cost of that default is that you have to watch for them yourself, especially when passing an argument to a parameter of the opposite signedness.
Lossy
Information is destroyed and cannot be recovered. Converting double to int discards the fractional part, and converting double to float discards precision:
#include <iostream>
int main()
{
double fromInt{ static_cast<double>(static_cast<int>(12.75)) };
double fromFloat{ static_cast<double>(static_cast<float>(5.87654321)) };
std::cout << fromInt << '\n';
std::cout << fromFloat << '\n';
return 0;
}
Output:
12
5.87654
12.75 became 12, and converting back gives 12.0 rather than 12.75. The float round trip fares no better, because a float holds roughly seven significant digits and the original had nine. Compilers generally do warn about implicit lossy conversions, and sometimes reject them outright.
When the Value Does Not Fit
Converting a value into a type whose range cannot hold it produces a result you almost certainly did not want:
#include <iostream>
int main()
{
int largeValue{ 4200 };
char smallChar = largeValue;
std::cout << static_cast<int>(smallChar) << '\n';
return 0;
}
Output:
104
A char holds -128 to 127, so 4200 cannot fit and only the low bits survive. Two rules govern what happens in general: overflow of an unsigned type is well defined and wraps, while overflow of a signed type is undefined behavior.
Within range, conversions behave as you would expect. Narrowing an int to a short, or a double to a float, works fine when the value fits, though a floating point narrowing may round:
#include <iomanip>
#include <iostream>
int main()
{
int whole{ 9 };
short smallWhole = whole;
double precise{ 0.6875 };
float lessPrecise = precise;
float lessAccurate = 0.987654321;
std::cout << smallWhole << '\n';
std::cout << lessPrecise << '\n';
std::cout << std::setprecision(9) << lessAccurate << '\n';
return 0;
}
Output:
9
0.6875
0.987654328
The last line is the rounding: nine significant digits went in, and a float could not hold them all.
Safety Can Depend on the Platform
Which category a conversion belongs to is not always fixed. int to double is normally value-preserving, because a 4-byte int fits comfortably inside an 8-byte double. On an architecture where int is also 8 bytes, it becomes lossy, since a double spends part of its 64 bits on the exponent and cannot represent every 64-bit integer exactly.
That effect is visible today using long long, which is at least 64 bits everywhere:
#include <iostream>
int main()
{
std::cout << static_cast<long long>(static_cast<double>(9007199254740993LL)) << '\n';
return 0;
}
Output:
9007199254740992
The final digit is gone.
When an Unsafe Conversion Is Acceptable
Avoid unsafe conversions where you can, but they are not always avoidable, and two situations make them reasonable:
- You can guarantee the values stay in the safe range. An
intconverts to anunsigned intwithout incident when you know it is never negative. - The lost information does not matter. Converting an
intto aboolthrows away everything except whether the value was zero, which is exactly what you asked for.
The compiler is a decent safety net for everything except the signed/unsigned case, so that is the one to keep in your own head.
Summary
Numeric conversions: conversions between fundamental types that the promotion rules do not cover, spanning integral to integral, floating point to floating point, either direction between the two, and either to bool.
Value-preserving: the destination represents every source value, as with int to long. Safe, silent, and reversible.
Reinterpretive: the value can change but no information is lost, as with signed to unsigned, where a negative value wraps to a large positive one. Converting back recovers the original.
Lossy: information is destroyed, as when double to int truncates 12.75 to 12, or double to float drops precision. Converting back does not recover the original.
Out of range: only part of the value survives. Unsigned overflow is well defined; signed overflow is undefined behavior.
Platform dependence: int to double is value-preserving on typical systems but lossy where both types are 8 bytes.
Warnings: compilers usually warn about lossy conversions, but leave signed/unsigned warnings off by default because those conversions are common and usually harmless.
How did you find this lesson?
Your rating helps us improve the content.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Converting Between Numeric Types - Quiz
Test your understanding of the lesson.
Practice Exercises
Numeric Conversions
Practice safe numeric conversions between different types. Learn about implicit conversions, potential data loss, and how to handle conversions safely using static_cast.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!