CodeMIRAI
0 XP

Type conversion and casting

5 min read 11 runnable examples Quiz included

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:

C
#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:

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:

C
#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:

AssignmentResultWhy
int i = 3.99;3the fraction is cut off, not rounded
int i = -9.99;-9cut toward zero
double d = 7;7.0exact
float f = 16777217;16777216.0a float has about 7 significant digits
unsigned char c = 300;44wraps round: 300 − 256
C
#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;
}
C
#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;
}
Output
16777216.0
44

A 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)ValueWhat happens
(double) total / count3.5total becomes 7.0, then a decimal division
total / (double) count3.5count becomes 2.0, same result
(double) (total / count)3.0the int division happens first; too late
(int) 3.993cut 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>:

C
#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;
}
Output
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
FunctionRounds2.5-2.5
roundto the nearest, halves away from zero3-3
floordown2-3
ceilup3-2
trunctoward 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:

C
#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;
}
Output
-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:

C
#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:

C
#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:

C
#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;
}
Output
42     42
42abc  42, then extra text: abc
abc    not a number
Score: 45/60

snprintf 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:

C
#include <stdio.h>

int main() {
    int x = 5;
    printf("%d %d %d\n", x != 0, !!x, !!0);
    return 0;
}

Summary

SituationWhat C does
int meets double in an operationthe int becomes a double
char or short in arithmeticpromoted to int first
double stored in an intthe fraction is cut off (toward zero)
a value too big for its new typelost precision (float) or wraparound (unsigned)
signed meets unsignedthe signed one becomes unsigned: watch out for negatives
you want roundinground, floor, ceil or lround from <math.h>
text to numberatoi/atof for trusted text, strtol to check
number to textsnprintf
Finished this lesson?Mark it complete to earn +20 XP and move your progress on.