Part of C in 100s

C in 100 Seconds: Type Casting | Episode 15

Celest KimCelest Kim

Video: C in 100 Seconds: Type Casting — Why 7/2 Is 3 and How to Fix It | Episode 15 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

C Type Casting: Why 7/2 Is 3 (and How to Fix It)

(float)a / b to force float division. (int)3.7 to truncate to 3. C's casts are explicit, low-level, and deceptively simple.

C casts let you tell the compiler "treat this value as a different type." Done right, they unblock conversions. Done wrong, they reinterpret bytes and silently break programs.

The basic shape

#include <stdio.h>

int main() {
  int a = 7, b = 2;
  printf("int / int = %d\n", a / b);             // 3
  printf("cast to float = %.2f\n", (float)a / b); // 3.50

  double pi = 3.14159;
  int truncated = (int)pi;
  printf("pi = %f\n", pi);                       // 3.141590
  printf("truncated = %d\n", truncated);         // 3

  char ch = 'A';
  printf("char %c = int %d\n", ch, ch);          // A = 65

  return 0;
}

Three different cast situations.

Integer division — and the fix

int a = 7, b = 2;
a / b                  // 3 — integer division
(float)a / b           // 3.5 — float division
(double)a / b          // 3.5 — same, more precise
a / (float)b           // 3.5 — either operand is enough

/ between two ints truncates toward zero. To get a decimal answer, at least one operand must be float or double.

The cast (float)a doesn't change a itself — it produces a new value of type float, used for this expression only. a remains int.

Float to int truncation

double pi = 3.14159;
int n = (int)pi;       // n = 3 (truncates toward zero)
int neg = (int)-3.7;   // neg = -3 (toward zero, NOT toward negative infinity)

Casting double-to-int truncates the fractional part. Toward zero, not toward negative infinity:

  • (int)3.7 → 3
  • (int)-3.7 → -3 (not -4!)

For "round to nearest," use roundf/round from <math.h>:

#include <math.h>
int rounded = (int)round(3.7);   // 4
int rounded2 = (int)round(-3.7); // -4

For "floor" (toward negative infinity): floor. For "ceiling": ceil.

char ↔ int

char ch = 'A';
int n = ch;       // n = 65 (no cast needed — char is just a small int)
char c2 = 65;     // 'A' (silent narrowing)
char c3 = 300;    // implementation-defined (300 doesn't fit in 1 signed byte)

char is a 1-byte integer. Conversion is automatic. Watch for narrowing — value out of range for char is implementation-defined.

For safe narrowing, check first:

int x = some_value;
if (x >= -128 && x <= 127) {
  char c = (char)x;
  // ...
}

Pointer casts

int n = 42;
void *p = &n;            // pointer to int → void pointer (no cast needed)
int *back = (int*)p;     // void* → int* (cast required)
char *bytes = (char*)&n; // reinterpret int as bytes (dangerous!)

void* is the "any pointer" type. Conversions to/from void* happen automatically. Conversions between specific pointer types require an explicit cast.

(char*)&n lets you read individual bytes of the int. Useful for low-level work (serialization, hashing) but easy to misuse — endianness affects byte order.

Implicit conversions

C does some conversions automatically:

int i = 5;
double d = i;          // implicit int → double (widening; safe)
float f = 3.14;        // implicit double → float (narrowing; warning)
short s = 100000;      // implicit int → short (likely warning; truncates)

Compiler warns on potentially-lossy conversions with -Wconversion. Pay attention.

sizeof and casts

int n = 1;
short s = (short)n;
// sizeof(int) = 4 (typical), sizeof(short) = 2
// upper bytes of n are dropped

Narrowing throws away high-order bytes. For values that fit, no problem. For values that don't, silent corruption.

Bit-level reinterpretation

float f = 3.14;
unsigned int bits = *(unsigned int*)&f;
printf("%x\n", bits);   // bytes of f as a hex number

*(unsigned int*)&f says "treat the bytes of f as an unsigned int and read the value." Strict-aliasing rules in modern C make this technically undefined; the safe form uses memcpy:

unsigned int bits;
memcpy(&bits, &f, sizeof(f));

Same effect, no aliasing violation. Compilers optimize it well.

Casting away const

void mutate(char *s) { s[0] = 'X'; }

const char str[] = "hello";
mutate((char*)str);  // technically allowed — but undefined behavior!

Casting away const compiles. But if the underlying data was actually const, modifying it is undefined behavior — for read-only memory (string literals), it crashes.

Casting away const is almost always wrong. If you legitimately need to modify, the storage shouldn't have been const in the first place.

Common mistakes

Cast applied to wrong subexpression. (float)a / b casts a. (float)(a / b) first does integer division (3), then casts (3.0). Different.

Silent narrowing. int x = 1000000; short s = x; truncates without warning by default. Use -Wconversion.

Pointer cast mismatch. int *p = (int*)str_ptr; *p = 42; writes 4 bytes where you might've expected 1. Endianness and alignment surprises.

Casting away const to modify. Crashes on read-only memory.

Cast to fix compile error without thinking. "Compiler complained, I added a cast." The cast suppresses the warning, but the underlying bug is still there. Understand why before casting.

When casts are right

  • Integer division → float: (double)total / count.
  • Function pointer types (callbacks of slightly different signatures).
  • Suppressing unused-variable warnings: (void)unused.
  • Pointer-arithmetic on void *: cast to char * for byte-by-byte stepping.
  • Discarding printf return: (void)printf("...") — explicit ignore.

Common patterns

// Convert int to bool-like (any nonzero → 1)
int b = !!x;          // double-bang idiom

// Get fractional part
double frac = pi - (int)pi;

// Divide rounding up
int ceil_div = (a + b - 1) / b;

What's next

Episode 16: header files. Multi-file projects, the #include pattern, declaration vs definition.

Recap

(type)expression to cast. Integer division truncates; cast either operand to float for decimal. Float-to-int truncates toward zero (not toward negative infinity). char is a tiny int — implicit conversions both ways. void* accepts any pointer; specific pointer types need explicit casts. (void*)&x to read bytes; use memcpy for portable bit-reinterpretation. Casting away const is undefined when the data is actually const. Casts don't fix bugs; they often hide them.

Next episode: header files.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.