Type Conversions
Introduction
Type conversion, also known as type casting, is the process of converting a value from one data type to another. C provides both implicit (automatic) and explicit (manual) type conversion mechanisms. Understanding how type conversions work is crucial for writing correct and efficient C programs, as improper conversions can lead to unexpected behavior, data loss, or undefined results.
This chapter will explore both implicit and explicit type conversions, their rules, potential pitfalls, and best practices for safe conversions.
Implicit Type Conversions (Coercion)
Implicit type conversions occur automatically when the compiler converts one data type to another without explicit programmer intervention. These conversions follow specific rules to ensure data integrity while maintaining program functionality.
Usual Arithmetic Conversions
When operators are applied to operands of different types, C performs usual arithmetic conversions to bring them to a common type:
#include <stdio.h>
int main() {
int i = 10;
float f = 3.14f;
double d = 2.5;
// int + float -> float
float result1 = i + f;
printf("int + float = %.2f (float)\n", result1);
// float + double -> double
double result2 = f + d;
printf("float + double = %.2f (double)\n", result2);
// int + double -> double
double result3 = i + d;
printf("int + double = %.2f (double)\n", result3);
return 0;
}Integer Promotion
Integer promotion is the automatic conversion of smaller integer types to int or unsigned int:
#include <stdio.h>
int main() {
char c = 'A';
short s = 100;
// char and short are promoted to int in expressions
int result = c + s;
printf("char + short = %d (int)\n", result);
// Promotion also happens with unary operators
int negated = -c;
printf("Negated char: %d\n", negated);
return 0;
}Conversion Hierarchy
C follows a specific hierarchy for implicit conversions:
- long double (highest)
- double
- float
- unsigned long long
- long long
- unsigned long
- long
- unsigned int
- int (lowest)
When operands of different types are used together, the lower type is converted to the higher type:
#include <stdio.h>
int main() {
char c = 'X';
short s = 1000;
int i = 50000;
float f = 3.14f;
double d = 2.71828;
// All converted to double
double result = c + s + i + f + d;
printf("Mixed arithmetic result: %.5f\n", result);
return 0;
}Boolean Context Conversions
In conditional expressions, all scalar types are implicitly converted to boolean values:
#include <stdio.h>
int main() {
int number = 42;
float zero_float = 0.0f;
char letter = 'A';
char null_char = '\0';
// Non-zero values are true, zero values are false
if (number) {
printf("Number %d is true\n", number);
}
if (!zero_float) {
printf("Zero float is false\n");
}
if (letter) {
printf("Character '%c' is true\n", letter);
}
if (!null_char) {
printf("Null character is false\n");
}
return 0;
}Explicit Type Conversions (Casting)
Explicit type conversions, or casting, allow programmers to manually convert values from one type to another using cast operators.
Cast Operator Syntax
The cast operator has the form (type) where type is the target data type:
#include <stdio.h>
int main() {
double pi = 3.14159;
int integer_pi = (int)pi; // Cast double to int
printf("Original: %.5f\n", pi);
printf("Casted: %d\n", integer_pi);
return 0;
}Common Casting Scenarios
Floating-Point to Integer
#include <stdio.h>
int main() {
float temperature = 98.6f;
int temp_int = (int)temperature;
printf("Float temperature: %.1f\n", temperature);
printf("Integer temperature: %d\n", temp_int);
// Note: Truncation, not rounding
float negative = -3.7f;
int neg_int = (int)negative;
printf("Negative float: %.1f -> int: %d\n", negative, neg_int);
return 0;
}Integer to Floating-Point
#include <stdio.h>
int main() {
int total = 100;
int count = 3;
// Without casting, integer division occurs
float average_int_div = total / count;
printf("Integer division: %d / %d = %.1f\n", total, count, average_int_div);
// With casting, floating-point division occurs
float average_float_div = (float)total / count;
printf("Float division: %.1f / %d = %.2f\n", (float)total, count, average_float_div);
return 0;
}Character to Integer and Vice Versa
#include <stdio.h>
int main() {
char letter = 'A';
int ascii_value = (int)letter;
printf("Character: %c\n", letter);
printf("ASCII value: %d\n", ascii_value);
int number = 66;
char character = (char)number;
printf("Number: %d\n", number);
printf("Character: %c\n", character);
return 0;
}Pointer Casting
C allows casting between different pointer types:
#include <stdio.h>
int main() {
int number = 42;
int *int_ptr = &number;
// Cast int pointer to void pointer
void *void_ptr = (void*)int_ptr;
// Cast void pointer back to int pointer
int *new_int_ptr = (int*)void_ptr;
printf("Original number: %d\n", number);
printf("Via pointer: %d\n", *new_int_ptr);
return 0;
}Potential Pitfalls and Data Loss
Truncation
#include <stdio.h>
int main() {
double large_number = 12345.6789;
int truncated = (int)large_number;
printf("Original: %.4f\n", large_number);
printf("Truncated: %d\n", truncated);
return 0;
}Overflow
#include <stdio.h>
#include <limits.h>
int main() {
long long big_number = 3000000000LL; // Larger than INT_MAX
int overflowed = (int)big_number;
printf("Big number: %lld\n", big_number);
printf("Overflowed: %d\n", overflowed); // Undefined behavior
return 0;
}Precision Loss
#include <stdio.h>
int main() {
float precise = 0.1f;
double more_precise = 0.1;
printf("Float: %.20f\n", precise);
printf("Double: %.20f\n", more_precise);
// Converting double to float loses precision
float less_precise = (float)more_precise;
printf("Casted double: %.20f\n", less_precise);
return 0;
}Safe Conversion Practices
Checking for Overflow
#include <stdio.h>
#include <limits.h>
int safe_int_cast(long long value) {
if (value > INT_MAX || value < INT_MIN) {
printf("Warning: Value %lld cannot fit in int\n", value);
return (value > 0) ? INT_MAX : INT_MIN;
}
return (int)value;
}
int main() {
long long big_value = 3000000000LL;
int safe_value = safe_int_cast(big_value);
printf("Safe value: %d\n", safe_value);
return 0;
}Using Standard Library Functions
#include <stdio.h>
#include <stdlib.h>
int main() {
char number_str[] = "12345";
long parsed_number = strtol(number_str, NULL, 10);
printf("String: %s\n", number_str);
printf("Parsed long: %ld\n", parsed_number);
// Safe conversion with range checking
if (parsed_number <= INT_MAX && parsed_number >= INT_MIN) {
int converted = (int)parsed_number;
printf("Converted to int: %d\n", converted);
} else {
printf("Number too large for int\n");
}
return 0;
}Conversion Functions
Mathematical Conversions
#include <stdio.h>
#include <math.h>
int main() {
double value = -3.7;
printf("Original: %.1f\n", value);
printf("Floor: %.1f\n", floor(value)); // Round down
printf("Ceil: %.1f\n", ceil(value)); // Round up
printf("Truncate: %.1f\n", trunc(value)); // Truncate towards zero
printf("Round: %.1f\n", round(value)); // Round to nearest
return 0;
}String Conversions
#include <stdio.h>
#include <stdlib.h>
int main() {
// String to integer
char int_str[] = "123";
int int_val = atoi(int_str);
printf("atoi(\"%s\") = %d\n", int_str, int_val);
// String to long
char long_str[] = "123456789";
long long_val = atol(long_str);
printf("atol(\"%s\") = %ld\n", long_str, long_val);
// String to double
char double_str[] = "3.14159";
double double_val = atof(double_str);
printf("atof(\"%s\") = %.5f\n", double_str, double_val);
// More robust conversions with error checking
char *endptr;
long strtol_val = strtol("456abc", &endptr, 10);
printf("strtol(\"456abc\") = %ld, stopped at: %s\n", strtol_val, endptr);
return 0;
}Practical Examples
Temperature Converter with Type Safety
#include <stdio.h>
// Safe conversion from Fahrenheit to Celsius
double fahrenheit_to_celsius(double fahrenheit) {
return (fahrenheit - 32.0) * 5.0 / 9.0;
}
// Safe conversion from Celsius to Fahrenheit
double celsius_to_fahrenheit(double celsius) {
return (celsius * 9.0 / 5.0) + 32.0;
}
int main() {
float user_input;
int choice;
printf("Temperature Converter\n");
printf("1. Fahrenheit to Celsius\n");
printf("2. Celsius to Fahrenheit\n");
printf("Enter choice: ");
scanf("%d", &choice);
if (choice == 1) {
printf("Enter temperature in Fahrenheit: ");
scanf("%f", &user_input);
double result = fahrenheit_to_celsius((double)user_input);
printf("%.2f°F = %.2f°C\n", user_input, result);
} else if (choice == 2) {
printf("Enter temperature in Celsius: ");
scanf("%f", &user_input);
double result = celsius_to_fahrenheit((double)user_input);
printf("%.2f°C = %.2f°F\n", user_input, result);
} else {
printf("Invalid choice\n");
}
return 0;
}Data Type Range Checker
#include <stdio.h>
#include <limits.h>
#include <float.h>
void check_int_range(long long value) {
printf("Checking value: %lld\n", value);
if (value > INT_MAX) {
printf(" Too large for int (max: %d)\n", INT_MAX);
} else if (value < INT_MIN) {
printf(" Too small for int (min: %d)\n", INT_MIN);
} else {
printf(" Fits in int range\n");
int converted = (int)value;
printf(" Converted value: %d\n", converted);
}
printf("\n");
}
int main() {
long long test_values[] = {
100LL,
3000000000LL,
-3000000000LL,
INT_MAX,
INT_MIN,
(long long)INT_MAX + 1,
(long long)INT_MIN - 1
};
int num_tests = sizeof(test_values) / sizeof(test_values[0]);
for (int i = 0; i < num_tests; i++) {
check_int_range(test_values[i]);
}
return 0;
}Mixed Type Calculator
#include <stdio.h>
int main() {
int int_val = 10;
float float_val = 3.14f;
double double_val = 2.71828;
char char_val = 'A'; // ASCII 65
printf("=== Mixed Type Arithmetic ===\n");
printf("int: %d\n", int_val);
printf("float: %.2f\n", float_val);
printf("double: %.5f\n", double_val);
printf("char: %c (ASCII: %d)\n", char_val, char_val);
printf("\n");
// Various combinations
printf("int + char: %d + %d = %d\n", int_val, char_val, int_val + char_val);
printf("float + int: %.2f + %d = %.2f\n", float_val, int_val, float_val + int_val);
printf("double + float: %.5f + %.2f = %.5f\n", double_val, float_val, double_val + float_val);
printf("char + double: %d + %.5f = %.5f\n", char_val, double_val, char_val + double_val);
// Explicit casting examples
printf("\n=== Explicit Casting ===\n");
printf("double to int: (int)%.5f = %d\n", double_val, (int)double_val);
printf("int to float: (float)%d = %.1f\n", int_val, (float)int_val);
printf("float to char: (char)%.2f = %c\n", float_val, (char)float_val);
return 0;
}Best Practices
1. Be Explicit About Conversions
// Good: Clear intent
float average = (float)total / count;
// Less clear: Relies on implicit conversion
float average = total / (float)count;2. Check for Data Loss
// Good: Range checking
if (big_value <= INT_MAX && big_value >= INT_MIN) {
int small_value = (int)big_value;
}3. Use Appropriate Functions for String Conversion
// Good: Error checking with strtol
char *endptr;
long value = strtol(string, &endptr, 10);
if (*endptr != '\0') {
printf("Invalid number format\n");
}4. Understand Truncation vs Rounding
#include <math.h>
// For truncation (default casting)
int truncated = (int)3.7; // Results in 3
// For proper rounding
int rounded = (int)round(3.7); // Results in 45. Be Careful with Signed/Unsigned Conversions
// Dangerous conversion
unsigned int u_value = 4000000000U;
int s_value = (int)u_value; // Implementation-defined behaviorSummary
In this chapter, you’ve learned about:
- Implicit Conversions: Automatic type conversions following C’s hierarchy
- Explicit Conversions: Manual casting using the cast operator
- Integer Promotion: Automatic promotion of smaller types to
int - Arithmetic Conversions: Rules for mixed-type arithmetic operations
- Potential Pitfalls: Truncation, overflow, and precision loss
- Safe Practices: Range checking and proper conversion techniques
- Standard Library Functions: atoi, atof, strtol for string conversions
- Best Practices: Writing safe and clear conversion code
Understanding type conversions is essential for writing robust C programs. In the next chapter, we’ll explore operators and expressions, which heavily rely on type conversion rules.