READMEC Programming

README

Basic Syntax / Typecasting

Concept Lesson
Beginner
4 min

Learning Objective

Understand Basic Syntax well enough to explain it, recognize it in C Programming, and apply it in a small task.

Why It Matters

This concept is part of the foundation that later lessons and projects assume you already understand.

SyntaxTypes Of ConversionImplicit Type Conversion Type CoercionInteger PromotionConversion Hierarchy
Private notes
0/8000

Notes stay private to your browser until account sync is configured.

README
2 min read18 headings

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.

Skill Check

Test this lesson

Answer 4 quick questions to lock in the lesson and feed your adaptive practice queue.

--
Score
0/4
Answered
Not attempted
Status
1

Which module does this lesson belong to?

2

Which section is covered in this lesson content?

3

Which term is most central to this lesson?

4

What is the best way to use this lesson for real learning?

Your answers save locally first, then sync when account storage is available.
Practice queue