Type Conversion and Casting

C converts values between types automatically in many situations — when you mix an int and a double, assign a value to a variable of another type, or pass an argument to a function. Knowing these rules, and when to cast explicitly, avoids silent loss of data.

Arithmetic conversions

When an operator gets two different number types, the "smaller" one is converted to the "larger" one first:

C
#include <stdio.h>

int main(void) {
    int items = 7;
    double weight = 0.5;
    printf("%f\n", items * weight);   // items becomes 7.0

    int a = 7, b = 2;
    printf("%d\n", a / b);            // both int: integer division
    printf("%f\n", a / 2.0);          // 2.0 is double: a is converted
    return 0;
}
Output
3.500000
3
3.500000

The rough order is: long double > double > float > integer types. Among integers, small types (char, short) are first promoted to int, and then the wider or unsigned type wins (lesson 8 showed what that means for signed/unsigned mixing).

Explicit casts

A cast converts a value to a type you choose: (type) expression. The classic case is getting a fractional average from integers:

C
#include <stdio.h>

int main(void) {
    int total = 17, count = 4;
    double wrong = total / count;
    double right = (double) total / count;
    printf("wrong: %.2f, right: %.2f\n", wrong, right);
    return 0;
}
Output
wrong: 4.00, right: 4.25

In wrong, the integer division happens first (giving 4) and only then is the result converted. Casting one operand before the division makes the whole calculation floating point.

Floating point to integer

Converting a floating-point value to an integer type truncates — it drops the fractional part, toward zero. To round, use round or lround from <math.h>:

C
#include <stdio.h>
#include <math.h>

int main(void) {
    double values[] = { 2.7, -2.7, 2.5 };
    for (int i = 0; i < 3; i++) {
        double v = values[i];
        printf("%5.1f -> cast %2d, lround %2ld, floor %4.1f, ceil %4.1f\n",
               v, (int) v, lround(v), floor(v), ceil(v));
    }
    return 0;
}
Output
  2.7 -> cast  2, lround  3, floor  2.0, ceil  3.0
 -2.7 -> cast -2, lround -3, floor -3.0, ceil -2.0
  2.5 -> cast  2, lround  3, floor  2.0, ceil  3.0

If the value doesn't fit in the integer type at all (say, 1e20 into an int), the conversion is undefined behaviour.

Narrowing conversions

Assigning to a smaller type can lose data. For unsigned targets the value wraps; for signed targets the result is implementation-defined. Compilers don't warn by default, but -Wconversion catches these:

C
#include <stdio.h>
#include <stdint.h>

int main(void) {
    int big = 300;
    uint8_t small = (uint8_t) big;   // 300 doesn't fit in 0..255
    printf("%d\n", small);

    double precise = 1.0 / 3.0;
    float less = (float) precise;
    printf("%.17f\n%.17f\n", precise, less);
    return 0;
}
Output
44
0.33333333333333331
0.33333334326744080

Writing the cast explicitly says "I know this narrows" — both to readers and to the compiler.

Characters and integers

C
#include <stdio.h>

int main(void) {
    char c = 'a';
    int code = c;              // char promoted to int
    char upper = c - 32;       // 'a' (97) - 32 = 'A' (65)
    printf("%c %d %c\n", c, code, upper);
    return 0;
}
Output
a 97 A

The <ctype.h> functions such as toupper are the proper way to change case (lesson 30); this just shows that characters are numbers.

Converting text to numbers

A string like "42" isn't a number; you parse it. strtol and strtod from <stdlib.h> do that and tell you where parsing stopped, so you can detect bad input:

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *inputs[] = { "42", "3.75", "12abc", "abc" };
    for (int i = 0; i < 4; i++) {
        char *end;
        long n = strtol(inputs[i], &end, 10);
        if (end == inputs[i]) {
            printf("%-6s -> not a number\n", inputs[i]);
        } else if (*end != '\0') {
            printf("%-6s -> %ld, then junk \"%s\"\n", inputs[i], n, end);
        } else {
            printf("%-6s -> %ld\n", inputs[i], n);
        }
    }
    printf("%.2f\n", strtod("3.75", NULL) * 2);
    return 0;
}
Output
42     -> 42
3.75   -> 3, then junk ".75"
12abc  -> 12, then junk "abc"
abc    -> not a number
7.50

The older atoi and atof are simpler but can't report errors: atoi("abc") just returns 0. The &end and *end syntax uses pointers, explained in lesson 31.

Try it yourself

Convert a temperature from Fahrenheit to Celsius. Try writing 5 / 9 instead of 5.0 / 9.0 and see what goes wrong.

Try it yourself · main.c
Edit the code, copy it into main.c, then compile and run it with cc -std=c17 -Wall main.c -o main && ./main. How to set up a C compiler
Output
98 F = 36.7 C
Rounded: 37 C