-
Casting is the act of reinterpreting or converting a value from one type to another.
Runtime casting
float to_float(int x) {
return (float)x;
}
convert :: proc(x: int) -> f32 {
return f32(x) // runtime conversion
}
-
If
xis not known at compile time, CPU executes a conversion instruction.
Value Transformation (conversion)
-
Changes representation
-
May require CPU instructions
-
There's runtime cost unless optimized away
int β float
u8 β u32
int
to
f32
-
Integer 32-bit
00000000 00000000 00000000 00001010 // 10
-
Float 32-bit IEEE-754
sign | exponent | mantissa
0 | 10000010 | 01000000000000000000000 // 10.0
-
Precision implications :
-
All integers up to
2^24 (16,777,216)are exact. -
Beyond that, precision loss occurs:
int x = 16777217; float y = (float)x;-
ybecomes16777216.
-
-
If can be computed at comptime (constant) :
-
Done by the compiler. Zero cost at runtime.
-
-
Runtime dedicated instructions in the floating-point unit (FPU) :
-
On x86 (SSE/AVX):
cvtsi2ss xmm0, eax ; int β float -
On ARM (NEON/VFP):
scvtf s0, w0 ; signed int β float -
Usually 1β3 CPU cycles
-
Uses the FPU / SIMD unit
-
May cause pipeline latency and register domain crossing (int β float regs)
-
Very fast, but not zero-cost.
-
-
Steps :
-
From
10(int) to10.0(f32).
-
Determine sign
-
10β positive β sign =0
-
-
Convert to binary scientific notation
-
10 = 1010β = 1.010 Γ 2Β³
-
-
Compute exponent
-
Bias for
f32=127 -
Stored exponent =
3 + 127 = 130β10000010
-
-
Fill mantissa
-
Drop leading
1.β store01000000000000000000000
-
-
Result:
0 | 10000010 | 01000000000000000000000
-
Type Reinterpretation
-
Often zero-cost
-
Dangerous if misused
-
Type-checked at compile time
-
"Are bitcasts purely a compiler thing?"
-
Mostly yesβbut not entirely.
-
When types live in different register classes:
int x; float y = bitcast(float, x);-
Compiler may emit:
movd xmm0, eax ; move bits from int reg β float reg-
Bits unchanged, but instruction needed due to register domains.
-
Pointer casting / reinterpretation
-
Under the hood: there is NO difference in representation. The only difference is how the compiler interprets that address.
Vec3* a;
void* b;
a = 0x1000
b = 0x1000
void* p = ...;
int* ip = (int*)p; // pointer reinterpretation
struct Vec3 { float x, y, z; };
Vec3* p; //typed pointer
-
A pointer is just something as
0x7FFEA0123450. The CPU does not know or care whether this points to an int, a struct, raw bytes, etc. -
A typed pointer is just for the compiler.
-
"At this address, there is a Vec3 laid out in memory.".
-
This allows for:
-
Correct dereferencing
-
p->x -
Compiler generates:
-
load from
address + offset_of(x)
-
-
Pointer arithmetic
-
p + 1 -
Becomes:
-
address + sizeof(Vec3) -
A pointer without typing (
rawptr,void*) can't perform pointer arithmetic, as "+1 what? 1 byte? 1 struct? 1 element?".
-
-
Type checking
-
Prevents invalid access (at compile time)
-
-
-
-
A
rawptr/void*is interpreted by the compiler as just an address, it doesn't know what is in there.
Integer Casting with same register sizes
-
Valid for :
-
u32<->i32. -
u64<->i64. -
uint<->int.
-
-
Example :
u32 a = 0xFFFFFFFF; i32 b = (i32)a;-
The bits are:
11111111 11111111 11111111 11111111-
As
u32β4294967295 -
As
i32β-1 -
Nothing changed except interpretation.
-
The compiler typically does nothing. It reuses the same register and emits no instructions.
-