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
- •Be explicit - Use casts when you mean to convert types
- •Avoid narrowing conversions - They can lose data
- •Watch for signed/unsigned mismatches - They cause subtle bugs
- •Use fixed-width types (from
<stdint.h>) for portable code - •Cast before division for floating-point results
- •Check for overflow when narrowing
- •Use parentheses around cast expressions for clarity
- •Prefer explicit casts over implicit conversions
🔑 Key Takeaways
- •Implicit conversion happens automatically (smaller → larger types)
- •Explicit casting uses
(type)expressionsyntax - •Integer division truncates - cast before dividing for float result
- •Narrowing conversions can lose data or cause overflow
- •Signed/unsigned mixing is dangerous in comparisons
- •void* is a generic pointer that needs casting before dereferencing
- •Always consider data loss when converting between types
⏭️ Next Topic
Continue to Comments and Documentation to learn how to properly document your code.