-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversions between floats and integers #221
Comments
We already have implicit constructors for these types then the incoming type will not lose any data, so the So I think maybe the choice to be made here is if that should be considered an unsafe operation, or it's fine to lose data as long as it's explicit. Since no UB occurs, this probably means it's "dangerous" from a "your software is likely to have bugs" POV but not "unsafe" from a "the compiler will ruin your life and you will struggle to debug this in production" POV. |
In this case, how to go from i32 y = i32::from_unchecked(x); // Unchecked usually comes with `unsafe_fn`...
i32 y = x.as<i32>();
i32 y = i32::from_lossy(x);
i32 y = x.cast<i32>(); noting that we already have |
An important sharp edge that's left unanswered right now is conversion between floats and integers. We have no i32::from(f32) or similar, no i32::from_unchecked(f32), no f32::from(i32), etc. We should look at what Rust defines for these conversions, because at best it's messy and there's tradeoffs to be made. |
f32 implements And it supports Casting from integers to floats:
Converting floats to integers:
C++ does not specify what happens when you convert from a primitive float to integer that can't be represented. |
Currently the integer types are From and they panic if the value is out of range. However there's three problems with this.
In Rust integers, From is only implemented when it can be done losslessly and without panic. We have the ctors for that already because it is important for conversion from primitives and literals, since they won't be type deduced to our numeric types.
|
Regarding integers, we should be informed by the PUN effort in chromium. When types are currently specified incorrectly, due to implicit conversions of primitive types, the fixes involved explicit casting (bit casting/truncation) but it was easy to do it wrong and produce bugs, while also being hard to convince people to just use checked_cast when u want to preserve the value and not the bit pattern. Another thought is how might rust have done this if they did not add the as keyword. Through a trait but they dont use From/Into for truncating conversions. Maybe a BitFrom trait (avoiding the type deduction of BitInto?) would be appropriate and possibly useful in other contexts too which makes it sound nice. u8::bit_from(-40_i64) // preserves bits |
Or maybe AsBits concept like AsRef which has as_bits() thus explicit conversion to the target type. FromBits could be the receiving side of AsBits then still with from_bits(x) |
FromBits is requires to support casting from primitives, as they can't be extended with AsBits. Then, if we have FromBits, we could provide sus::as_bits(x) that calls T::from_bits(x). But is there a point to that? Ah, but if we want to cast back to primitives too, then we need Actually, if we take it all outside of the class then one concept is sufficient for both directions. It could plausibly be named FromBits or AsBits, though I think the latter reads a bit more correctly with the template parameter. Either way the type being constructed is the type parameter. // With from_bits.
unsigned int a = 2;
auto b = sus::from_bits<i16>(a);
// With as_bits.
unsigned int a = 2;
auto b = sus::as_bits<i16>(a); The concept would be satisfied by a template specialization, like with template <class To, class From>
struct AsBitsImpl;
template <class To, class From>
concept AsBits = requires (const From& from) {
{ AsBitsImpl<To>::from_bits(from) } -> std::same_as<To>;
};
template <class To, class From>
To as_bits(const From& from) { return AsBitsImpl<To>::from_bits(from); } Which allows restricting on types that be bit-converted into X: i32 add_10(AsBits<i32> auto x) {
return sus::as_bits<i32>(x) + 10;
} |
This gives a cast-like syntax for fallably converting to a type in a value-preserving way for chromium#221.
as_bits<T>() is like into() except it: - does not deduce the type, you must specify it - does not preserve the meaning, it preserves the bits instead - can be lossy, including trucation This provides bitwise conversions between integers for chromium#221.
This gives a cast-like syntax for fallably converting to a type in a value-preserving way for chromium#221.
This gives a cast-like syntax for fallably converting to a type in a value-preserving way for chromium#221.
Staring at naming some more and wanting to follow good guidelines (https://rust-lang.github.io/api-guidelines/naming.html) for consistency, AsBits should be ToBits, and it should be sus::to_bits. |
Instead of a strict bit preservation between int and float, it would be better to follow the rules of #221 (comment) assuming they are ~compatible with static_cast but with less UB. Have defined float->integer casting to match them, with well defined behaviour for NAN and OOB values. For in-bound values it has the same defined behaviour as static_cast did. integer->float looks.. harder =) for another day. |
Also.. if it's not strict bit preservation then I want a better name than ToBits probably. But to_bits is nice and short, something that represents the lossyness of it all. Current best idea: |
as_bits<T>() is like into() except it: - does not deduce the type, you must specify it - does not preserve the meaning, it preserves the bits instead - can be lossy, including trucation This provides bitwise conversions between integers for chromium#221.
This gives a cast-like syntax for fallably converting to a type in a value-preserving way for chromium#221.
It's super non-trivial to match rust's behaviour in conversions from int to float. C++ leaves it as implementation defined behaviour for which direction to round toward a representable floating point value, while Rust follows IEEE specs better and has a defined strategy of But also it's probably problematic to do something other than what static_cast does anyway because a) it would be slower and b) it would make converting code from static_cast to something safer (no UB) suddenly get risky. So to_bits() is now roughly defined as doing a static_cast but with defined behaviour for edge cases instead of UB. |
This is all done now in #289 except we need a better name than Current candidates:
|
as_bits<T>() is like into() except it: - does not deduce the type, you must specify it - does not preserve the meaning, it preserves the bits instead - can be lossy, including trucation This provides bitwise conversions between integers for #221.
This gives a cast-like syntax for fallably converting to a type in a value-preserving way for #221.
Not listed above is |
Other conversions that we're missing:
|
These were stabilized in Rust 1.73. https://blog.rust-lang.org/2023/10/05/Rust-1.73.0.html Part of chromium#221.
These were stabilized in Rust 1.73. https://blog.rust-lang.org/2023/10/05/Rust-1.73.0.html Part of #221.
https://doc.rust-lang.org/reference/expressions/operator-expr.html#type-cast-expressions
The
as
keyword in Rust will convert between types without panicing, and can introduce truncation.We could provide
3_i32.as<u32>()
orsus::as<u32>(3_i32)
(the latter feels weird).This is a lot like static_cast in C++ which is also a notorious cause of bugs... do we want to provide this in place of
i32::from_unchecked(unsafe_fn, x)
which already does a static_cast?The text was updated successfully, but these errors were encountered: