If you never work with floating-point values in C++, you can ignore this.
TLDR: If in doubt, you want base::ClampRound().
All the following ways of converting a float to an int are wrong:
float f = ...;
int i1 = static_cast<int>(f); // Wrong (1)
int i2 = std::floor(f); // Wrong (2)
int i3 = std::round(f); // Also wrong (2)
int i4 = static_cast<int>(f + 0.5f); // Wrong (3)
int i5 = base::saturated_cast<int>(f + ((f > 0) ? 0.5f : -0.5f)); // Wrong (4)
Here's why:
- floats can represent values outside the range of an int, so implicit-casting or static_casting is unsafe due to potential overflow. If you want "truncate"-like behavior, use base::saturated_cast.
- std::ceil/floor/round() do not return ints, they return floating-point types, so the bullet above still applies. If you want behaviors like these, use base::ClampCeil/Floor/Round().
- Trying to round manually by adding 0.5 and truncating is generally the wrong behavior for negative numbers. Plus, the bullets above still apply. Use base::ClampRound().
- Due to floating-point precision loss, manually adding 0.5 and compensating for negative numbers by flipping the sign is STILL wrong for certain edge cases (IIRC, 0.5 - 1ULP). IEEE754 is hard. Use base::ClampRound().
Thank you, you can stop here if you want.
EXTRA BONUS NERD-OUT
ALL of these are slow in hardware and exhibit statistical bias compared to std::nearbyint(), which is "round using the current hardware rounding mode, which is likely 'round to nearest, break ties towards even'" (like std::round(), except values ending in exactly .5 go to the nearest even number instead of away from zero). Of course this returns a floating-point number so you still need base::saturated_cast. Don't use std::rint() (which is like std::nearbyint() but can raise a floating-point exception), and don't use std::lrint() or std::llrint() (because they return long and long long, which we don't allow, and because you're pinky-promising not to overflow, which is less safe than just guaranteeing the code is robust).
PK