Docs

README

Type Casting in C

📖 Introduction

Type casting (also called type conversion) is the process of converting a value from one data type to another. Understanding type casting is crucial for writing correct C programs, especially when working with mixed data types.


🎯 Types of Conversion

                    Type Conversion
                          │
            ┌─────────────┴─────────────┐
            │                           │
        Implicit                    Explicit
   (Automatic/Coercion)          (Type Casting)
            │                           │
   Compiler does it              Programmer does it
   automatically                 using cast operator
            │                           │
   int → float                   (float)num
   char → int                    (int*)ptr

🔄 Implicit Type Conversion (Type Coercion)

The compiler automatically converts one type to another when needed.

Integer Promotion:

When smaller integer types are used in expressions, they're promoted to int:

char c = 'A';        // 1 byte
short s = 100;       // 2 bytes
int result = c + s;  // Both promoted to int before addition

Conversion Hierarchy:

When mixing types, the "lower" type is converted to the "higher" type:

              long double      (highest)
                   ↑
               double
                   ↑
                float
                   ↑
           unsigned long long
                   ↑
             long long
                   ↑
            unsigned long
                   ↑
                 long
                   ↑
            unsigned int
                   ↑
                  int         (base)
                   ↑
            short / char      (promoted to int)

Examples:

// Integer + Float → Float
int a = 5;
float b = 2.5f;
float result = a + b;  // a converted to 5.0f
printf("%.2f\n", result);  // 7.50

// Integer / Integer → Integer (truncation!)
int x = 5, y = 2;
int z = x / y;  // 2, not 2.5

// Integer / Float → Float
float w = x / b;  // 5 / 2.5 = 2.0 (x converted to float)

// Character arithmetic
char c = 'A';  // ASCII 65
int i = c + 1;  // 66
char d = c + 1;  // 'B'

Assignment Conversion:

When assigning, the right side is converted to the type of the left side:

int i;
float f = 3.7f;

i = f;        // i = 3 (truncated, not rounded)
f = 10;       // f = 10.0

🎯 Explicit Type Casting

The programmer explicitly requests a type conversion using the cast operator.

Syntax:

(target_type) expression

Basic Examples:

int a = 5, b = 2;

// Integer division (truncates)
float wrong = a / b;      // 2.0 (5/2=2, then 2→2.0)

// Cast to get float result
float correct = (float)a / b;      // 2.5 (5.0/2=2.5)
float also_correct = a / (float)b;  // 2.5 (5/2.0=2.5)
float both = (float)a / (float)b;   // 2.5

// Casting result
float wrong2 = (float)(a / b);     // 2.0 (5/2=2, then 2→2.0)

printf("wrong: %.2f\n", wrong);     // 2.00
printf("correct: %.2f\n", correct); // 2.50

Character and Integer:

// Character to integer
char c = 'A';
int ascii = (int)c;  // 65

// Integer to character
int num = 66;
char letter = (char)num;  // 'B'

// Digit character to number
char digit = '7';
int value = digit - '0';  // 7 (not cast, but subtraction)

Floating-Point Precision:

double d = 3.14159265358979;
float f = (float)d;  // Loses precision

printf("double: %.15f\n", d);
printf("float:  %.15f\n", f);

// Float to int (truncation, not rounding)
float f2 = 3.7f;
int i1 = (int)f2;      // 3
int i2 = (int)(f2 + 0.5f);  // 4 (poor man's rounding)

📍 Pointer Casting

Casting between pointer types is common in C.

void* Casting:

void* is a generic pointer that can hold any pointer type:

int x = 42;
void *generic = &x;        // No cast needed from any pointer to void*
int *specific = (int*)generic;  // Cast needed from void* to specific type

printf("%d\n", *specific);  // 42

Pointer Arithmetic with Cast:

int arr[] = {1, 2, 3, 4, 5};
int *ptr = arr;

// Treat as byte array
unsigned char *bytes = (unsigned char*)arr;
printf("First byte: 0x%02X\n", bytes[0]);

Function Pointers:

void sayHello() {
    printf("Hello!\n");
}

// Cast function pointer
void (*funcPtr)() = sayHello;
funcPtr();

// Cast to void* (for storage, not calling)
void *voidPtr = (void*)sayHello;

📊 Integer Type Casting

Widening (Safe):

Converting to a larger type preserves the value:

char c = 127;
short s = c;      // Safe: 127
int i = s;        // Safe: 127
long l = i;       // Safe: 127

Narrowing (Dangerous):

Converting to a smaller type may lose data:

int i = 300;
char c = (char)i;  // Undefined or wrapped: 300 % 256 = 44
printf("%d\n", c);  // 44 (or -12 depending on signed char)

long l = 4000000000L;
int i2 = (int)l;   // May overflow!
printf("%d\n", i2);  // Unexpected value

Signed/Unsigned Conversion:

// Unsigned to signed (may become negative)
unsigned int u = 4000000000U;
int s = (int)u;
printf("unsigned %u → signed %d\n", u, s);

// Signed to unsigned (negative becomes large positive)
int negative = -1;
unsigned int positive = (unsigned int)negative;
printf("signed %d → unsigned %u\n", negative, positive);

// Bit pattern example
char c = -1;  // 11111111 in binary
unsigned char uc = (unsigned char)c;  // Still 11111111 = 255
printf("signed char %d → unsigned char %u\n", c, uc);

⚠️ Common Pitfalls

1. Integer Division Trap:

// WRONG
float average = sum / count;  // If sum and count are int

// CORRECT
float average = (float)sum / count;

2. Comparison with Different Signs:

unsigned int u = 10;
int i = -1;

if (i < u) {  // WRONG! -1 becomes a huge positive number
    printf("i is less\n");
} else {
    printf("i is greater\n");  // This prints!
}

// CORRECT
if (i < 0 || (unsigned int)i < u) {
    printf("i is less\n");
}

3. Truncation vs Rounding:

float f = 3.9f;
int i = (int)f;  // 3, not 4 (truncation)

// Proper rounding
int rounded = (int)(f + 0.5f);  // 4

// Using math.h
#include <math.h>
int rounded2 = (int)roundf(f);  // 4
int floored = (int)floorf(f);   // 3
int ceiled = (int)ceilf(f);     // 4

4. Pointer Type Punning:

float f = 3.14f;

// WRONG (undefined behavior - breaks strict aliasing)
int *ip = (int*)&f;
int bits = *ip;  // May not work as expected

// CORRECT (use union or memcpy)
union {
    float f;
    int i;
} converter;
converter.f = 3.14f;
int bits = converter.i;  // Proper way to inspect bits

5. Loss of Precision:

long long big = 9223372036854775807LL;
double d = (double)big;  // Loses precision!
printf("Original: %lld\n", big);
printf("As double: %.0f\n", d);  // Different value

🔧 Practical Examples

1. Safe Integer Division:

float safeDivide(int numerator, int denominator) {
    if (denominator == 0) {
        return 0.0f;  // Or handle error
    }
    return (float)numerator / denominator;
}

2. Byte Extraction:

void extractBytes(int value) {
    unsigned char byte0 = (unsigned char)(value & 0xFF);
    unsigned char byte1 = (unsigned char)((value >> 8) & 0xFF);
    unsigned char byte2 = (unsigned char)((value >> 16) & 0xFF);
    unsigned char byte3 = (unsigned char)((value >> 24) & 0xFF);

    printf("0x%08X = bytes: %02X %02X %02X %02X\n",
           value, byte3, byte2, byte1, byte0);
}

3. Generic Swap Function:

void genericSwap(void *a, void *b, size_t size) {
    unsigned char *pa = (unsigned char*)a;
    unsigned char *pb = (unsigned char*)b;
    unsigned char temp;

    for (size_t i = 0; i < size; i++) {
        temp = pa[i];
        pa[i] = pb[i];
        pb[i] = temp;
    }
}

// Usage
int x = 5, y = 10;
genericSwap(&x, &y, sizeof(int));

4. Number to String Digit:

char digitToChar(int digit) {
    if (digit < 0 || digit > 9) {
        return '?';
    }
    return (char)('0' + digit);  // '0' is 48 in ASCII
}

📏 Size and Alignment Considerations

#include <stdint.h>

// Fixed-size types for predictable behavior
int8_t   i8  = 127;       // Exactly 8 bits
int16_t  i16 = 32767;     // Exactly 16 bits
int32_t  i32 = 2147483647; // Exactly 32 bits
int64_t  i64 = 9223372036854775807LL; // Exactly 64 bits

// Casting between fixed sizes
i16 = (int16_t)i32;  // May lose data
i32 = (int32_t)i16;  // Safe widening

// Pointer size consideration
intptr_t ptrAsInt = (intptr_t)&i32;  // Pointer to integer
void *intAsPtr = (void*)ptrAsInt;    // Integer back to pointer

✅ Best Practices

  1. Be explicit - Use casts when you mean to convert types
  2. Avoid narrowing conversions - They can lose data
  3. Watch for signed/unsigned mismatches - They cause subtle bugs
  4. Use fixed-width types (from <stdint.h>) for portable code
  5. Cast before division for floating-point results
  6. Check for overflow when narrowing
  7. Use parentheses around cast expressions for clarity
  8. Prefer explicit casts over implicit conversions

🔑 Key Takeaways

  1. Implicit conversion happens automatically (smaller → larger types)
  2. Explicit casting uses (type)expression syntax
  3. Integer division truncates - cast before dividing for float result
  4. Narrowing conversions can lose data or cause overflow
  5. Signed/unsigned mixing is dangerous in comparisons
  6. void* is a generic pointer that needs casting before dereferencing
  7. Always consider data loss when converting between types

⏭️ Next Topic

Continue to Comments and Documentation to learn how to properly document your code.

README - C Programming Tutorial | DeepML