Type conversion and casting
C converts values between types all the time, often without saying so. Most conversions are exactly what you'd want; a few quietly change your numbers. Knowing the rules turns those surprises into decisions.
Mixed arithmetic: the wider type wins
When an operator gets two different types, C converts the narrower one to the wider one first. An int meeting a double becomes a double:
#include <stdio.h>
int main() {
int qty = 5;
double price = 2.5;
printf("%f\n", qty * price); // qty becomes 5.0 first
printf("%f\n", 1.0 / 3); // 3 becomes 3.0
printf("%d\n", 1 / 3); // both int: integer division
return 0;
}The order of widening is roughly char → short → int → long long → float → double. The conversion happens per operation, which leads to the most common conversion bug in C:
#include <stdio.h>
int main() {
int total = 7, count = 2;
double wrong = total / count; // 7 / 2 is done in int: 3, then stored as 3.0
double right = (double) total / count; // 7.0 / 2: 3.5
printf("%.2f %.2f\n", wrong, right);
int got = 45, out_of = 60;
printf("%d%%\n", got * 100 / out_of); // integer maths: 75
printf("%.1f\n", got * 100.0 / out_of); // 100.0 makes it decimal: 75.0
return 0;
}Storing the result in a double doesn't help wrong: by the time it's stored, the division has already thrown the fraction away. A cast or a decimal literal such as 100.0 makes the calculation itself decimal.
Integer promotion
char and short values are promoted to int before any arithmetic, so a small type can't overflow in the middle of a sum:
#include <stdio.h>
int main() {
char a = 100, b = 100;
int sum = a + b; // a and b become int: 200 fits easily
printf("%d\n", sum);
return 0;
}Assignment: when the value doesn't fit
Storing a value converts it to the variable's type. Widening (an int into a double) is always exact. Narrowing can lose information:
| Assignment | Result | Why |
|---|---|---|
int i = 3.99; | 3 | the fraction is cut off, not rounded |
int i = -9.99; | -9 | cut toward zero |
double d = 7; | 7.0 | exact |
float f = 16777217; | 16777216.0 | a float has about 7 significant digits |
unsigned char c = 300; | 44 | wraps round: 300 − 256 |
#include <stdio.h>
int main() {
double price = 9.99;
int whole = price; // 9: the fraction goes
int neg = -9.99; // -9: toward zero
printf("%d %d\n", whole, neg);
return 0;
}#include <stdio.h>
int main() {
int big = 16777217;
float f = big; // too many digits for a float
printf("%.1f\n", f);
unsigned char c = 300; // too big for one byte: wraps round
printf("%d\n", c);
return 0;
}16777216.0
44A good compiler warns about narrowing when you ask it to (-Wconversion in GCC). When you mean it, say so with a cast, so the next reader knows it was deliberate.
Explicit casts
A cast is a type in brackets in front of a value: (double) total. It converts that one value, once; the variable itself is unchanged. Where the cast goes matters:
| Expression (total 7, count 2) | Value | What happens |
|---|---|---|
(double) total / count | 3.5 | total becomes 7.0, then a decimal division |
total / (double) count | 3.5 | count becomes 2.0, same result |
(double) (total / count) | 3.0 | the int division happens first; too late |
(int) 3.99 | 3 | cut off |
(char) 65 | 'A' | the character with code 65 |
Rounding, not cutting
Converting to int always truncates (cuts toward zero). To round, use <math.h>:
#include <stdio.h>
#include <math.h>
int main() {
double x = 2.5, y = -2.5, z = 2.7;
printf("round: %.1f %.1f %.1f\n", round(x), round(y), round(z));
printf("floor: %.1f %.1f %.1f\n", floor(x), floor(y), floor(z));
printf("ceil: %.1f %.1f %.1f\n", ceil(x), ceil(y), ceil(z));
printf("trunc: %.1f %.1f %.1f\n", trunc(x), trunc(y), trunc(z));
printf("as an int: %ld\n", lround(2.5));
printf("the +0.5 trick: %d %d\n", (int) (2.5 + 0.5), (int) (-2.5 + 0.5));
return 0;
}round: 3.0 -3.0 3.0
floor: 2.0 -3.0 2.0
ceil: 3.0 -2.0 3.0
trunc: 2.0 -2.0 2.0
as an int: 3
the +0.5 trick: 3 -2| Function | Rounds | 2.5 | -2.5 |
|---|---|---|---|
round | to the nearest, halves away from zero | 3 | -3 |
floor | down | 2 | -3 |
ceil | up | 3 | -2 |
trunc | toward zero (what a cast does) | 2 | -2 |
The old trick (int) (x + 0.5) rounds positive numbers correctly but gets negative halves wrong (-2.5 gives -2), so prefer round or lround, which returns the result as a whole-number type.
The signed and unsigned trap
When a signed and an unsigned value meet, the signed one becomes unsigned. A negative number then turns into a huge positive one:
#include <stdio.h>
int main() {
int minus_one = -1;
unsigned int one = 1;
if (minus_one < one) {
printf("-1 is less than 1\n");
} else {
printf("-1 is NOT less than 1?!\n");
}
printf("-1 as unsigned: %u\n", (unsigned int) minus_one);
return 0;
}-1 is NOT less than 1?!
-1 as unsigned: 4294967295-1 became 4294967295, which is bigger than 1. The same trap waits in sizeof, which gives an unsigned value: comparing a negative int with sizeof something goes wrong the same way. Keep comparisons between values of the same signedness; GCC's -Wall -Wextra warns about mixed ones.
Characters and digits
Characters are numbers, so converting between a digit character and its value is just arithmetic:
#include <stdio.h>
int main() {
char c = '7';
int value = c - '0'; // '7' to 7
int d = 4;
char shown = '0' + d; // 4 to '4'
printf("%d %c\n", value + 1, shown);
char letter = 'g';
char upper = letter - 'a' + 'A'; // 'g' to 'G'
printf("%c\n", upper);
return 0;
}<ctype.h> has ready-made versions: toupper, tolower, isdigit, isalpha.
Text to numbers, and back
A number typed as text (from a file or a char array) has to be converted before you can calculate with it:
#include <stdio.h>
#include <stdlib.h>
int main() {
int n = atoi("123"); // ASCII to int
double d = atof("3.75"); // ASCII to floating point
printf("%d %.2f\n", n + 1, d * 2);
return 0;
}atoi and atof can't report a mistake: atoi("abc") simply returns 0, the same as atoi("0"). When the text might not be a number, use strtol (string to long), which tells you where it stopped reading:
#include <stdio.h>
#include <stdlib.h>
int main() {
const char *inputs[] = { "42", "42abc", "abc" };
for (int i = 0; i < 3; i++) {
char *end;
long v = strtol(inputs[i], &end, 10); // 10: decimal
if (end == inputs[i]) {
printf("%-6s not a number\n", inputs[i]);
} else if (*end != '\0') {
printf("%-6s %ld, then extra text: %s\n", inputs[i], v, end);
} else {
printf("%-6s %ld\n", inputs[i], v);
}
}
char label[20];
snprintf(label, sizeof label, "Score: %d/%d", 45, 60); // number to text
printf("%s\n", label);
return 0;
}42 42
42abc 42, then extra text: abc
abc not a number
Score: 45/60snprintf is printf into a char array, and never writes more than the size you give it.
True and false
Any number converts to true or false in a condition: 0 is false, anything else true. !!x turns any number into exactly 1 or 0:
#include <stdio.h>
int main() {
int x = 5;
printf("%d %d %d\n", x != 0, !!x, !!0);
return 0;
}Summary
| Situation | What C does |
|---|---|
int meets double in an operation | the int becomes a double |
char or short in arithmetic | promoted to int first |
double stored in an int | the fraction is cut off (toward zero) |
| a value too big for its new type | lost precision (float) or wraparound (unsigned) |
| signed meets unsigned | the signed one becomes unsigned: watch out for negatives |
| you want rounding | round, floor, ceil or lround from <math.h> |
| text to number | atoi/atof for trusted text, strtol to check |
| number to text | snprintf |