-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
num::WrappingFrom
trait for conversions between integers
#3703
base: master
Are you sure you want to change the base?
Conversation
An alternative is to have it as inherent methods. That solves the problems with |
A few thoughts:
|
I look forward to being able to write fewer
|
It turns out that | ||
- `NonZero<u8>` has 2⁸ - 1 = 255 × 1 values | ||
- `NonZero<u16>` has 2¹⁶ - 1 = 255 × 257 values | ||
- `NonZero<u32>` has 2³² - 1 = 255 × 16843009 values | ||
- `NonZero<u64>` has 2⁶⁴ - 1 = 255 × 72340172838076673 values | ||
- `NonZero<u128>` has 2¹²⁸ - 1 = 255 × 1334440654591915542993625911497130241 values | ||
|
||
(This works for anything that's a multiple of octets, as `0xFF…FF = 0xFF * 0x01…01`.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It works for more than just factors of 0xFF
.
also, you should use latex math syntax, since that does work on github and doesn't use a bunch of unicode characters that may not render properly (e.g. they didn't for me with the monospace font on my phone). Rendered example:
-
NonZero<u128>
has$2^{128} - 1 = (2^8 - 1) \times (2^8 + 1) \times (2^{16} + 1) \times (2^{32} + 1) \times (2^{64} + 1)$ values
It turns out that | |
- `NonZero<u8>` has 2⁸ - 1 = 255 × 1 values | |
- `NonZero<u16>` has 2¹⁶ - 1 = 255 × 257 values | |
- `NonZero<u32>` has 2³² - 1 = 255 × 16843009 values | |
- `NonZero<u64>` has 2⁶⁴ - 1 = 255 × 72340172838076673 values | |
- `NonZero<u128>` has 2¹²⁸ - 1 = 255 × 1334440654591915542993625911497130241 values | |
(This works for anything that's a multiple of octets, as `0xFF…FF = 0xFF * 0x01…01`.) | |
It turns out that | |
- `NonZero<u8>` has $2^8 - 1 = (2^8 - 1)$ values | |
- `NonZero<u16>` has $2^{16} - 1 = (2^8 - 1) \times (2^8 + 1)$ values | |
- `NonZero<u32>` has $2^{32} - 1 = (2^8 - 1) \times (2^8 + 1) \times (2^{16} + 1)$ values | |
- `NonZero<u64>` has $2^{64} - 1 = (2^8 - 1) \times (2^8 + 1) \times (2^{16} + 1) \times (2^{32} + 1)$ values | |
- `NonZero<u128>` has $2^{128} - 1 = (2^8 - 1) \times (2^8 + 1) \times (2^{16} + 1) \times (2^{32} + 1) \times (2^{64} + 1)$ values | |
(This works for anything where the number of bits is a multiple of the number of bits of a smaller type, as `0xFF…FF = 0xFF * 0x01…01` and so on for larger groups of `F`s.) |
The only examples I can think of are not exactly well known (e.g. Galois Fields): impl WrappingFrom<GaloisField<9>> for GaloisField<3> {
...
} though a better example might be: impl WrappingFrom<Angle> for PointOnUnitCircle { // both 0 and 2 pi radians convert to the same point
...
} |
that part's easier:
|
I think that having a |
Thanks for writing a more focussed RFC @scottmcm. I agree that something like this should exist, both for usage in generics, and with an eventual goal of making Not distinguishing between truncating and extending casts is a minor flaw, but likely better than attempting to distinguish these cases with separate traits.
My take is that if
Because |
One thing I've been thinking here is that But overall it's not obvious to me in which contexts you'd want to require a truncation, and trying to write a trait for truncation specifically reopens a bunch of hard problems about |
Here’s one I can think of: If you have an integer that’s uniformly distributed across the type’s entire range (e.g. a hash), then truncating it produces a uniformly distributed value of the smaller type. If you convert it to a larger type, then now you have added zero bits, so it no longer covers the entire range. I don't know of any specific applications that want this property and aren't non-generic or better served by different tools, though. |
I like this proposal, but I think there is a more generic need for lossy conversions than just integers, for example you could imagine a trait trait FromLossy<T> {
fn from_lossy(t: &T) -> Self;
} that converts from a wider set into a narrower one, but infallibly, like a "best effort" It would be really useful to be able to, for example, do let s: String = b"hello\xffworld".into_lossy(); |
@FHTMitchell while that may be true, it is a very different sort of conversion to integer wrapping conversions, so I don't think here is a good place to discuss it. Feel free to go back to #2484 (or possibly start a new, focussed, RFC). |
Yeah, I definitely don't like the idea of combining float and integer conversions into one trait, since for floats, the result is actual rounding, whereas for integers, your result could be totally different from the original number. That's the main purpose of having a dedicated |
In particular, as the #2484 discussion shows, it's very hard to define a useful generic concept of "lossy" that isn't just "meh, it does something, good luck" -- because you need a precise definition for generic code to make any sense. And, for example, So as others have said, this is one particular type of conversion. If there are others, those will be other RFCs. |
Co-authored-by: teor <[email protected]>
Also prior art: #3415 Summary of the discussion:
|
an idea for if input.is_finite() {
// can be done without actually needing an intermediate bigint or allocation
Output::wrapping_from(input.truncate().to_bigint())
} else {
0
} |
|
if you think of wrapping as returning just the bits that fit in the target integer, JavaScript-style semantics are reasonable as wrapping float -> int: though this only works for negative numbers if you think of treating the float as being in sign-magnitude and extracting the bits as an unsigned integer, then negating the result if the float was negative (kinda like sign-magnitude to twos complement conversion). Infinities are treated as being
something else being more useful doesn't mean wrapping can't also be useful... |
/// For an integer `a: Big` and a smaller integer type `Small`, truncating with `wrapping_from` | ||
/// commutes with `wrapping_add`, so `Small::wrapping_from(a).wrapping_add(b)` and | ||
/// `Small::wrapping_from(a.wrapping_add(b))` will give the same result. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is "b" here? I'm guessing it is supposed to be something like "for all b
in Small
"?
```rust | ||
// in core::num (and std::num) | ||
|
||
/// Performs potentially-lossy conversions in a quantized numeric lattice. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is a "quantized numeric lattice"?
Could this be something like "Performs potentially-lossy conversion for integral types"? Does it make sense for non-integer types? For floating points, I'm not sure when you would ever want it to wrap instead of produce an infinite value if the original is out of range.
This also doesn't really describe in what way the conversion is lossy, although that is discussed elsewhere in the RFC. It would probably be good to have simple description (it truncates), and a more formal description of the semantics, perhaps something like:
For an integer
a: Big
and a smaller integer typeSmall
,Small::wrapping_from(a)
returns a valueb
inSmall
such thata == b + (R * n)
for somen
inBig
whereR
is the number of possible values inSmall
( that is effectivelySmall::MAX - Small::MIN + 1
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
returns a value
b
inSmall
such thata == b + (R * n)
for somen
inBig
whereR
is the number of possible
I would phrase that:
returns the unique value Small
such that, in unlimited range mathematical integers,
note I'm using latex math notation $a = b + n \times R$
This talks about |
I think you mean adding |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few typos:
|
||
`WrappingFrom` is specific to *numbers*. You can't use it for things like `String: From<&str>` which we've used a bunch. | ||
|
||
It's is implemented for the numeric widening conversions that also exist in `From`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's is implemented for the numeric widening conversions that also exist in `From`. | |
It's implemented for the numeric widening conversions that also exist in `From`. |
fn wrapping_from(value: T) -> Self; | ||
} | ||
|
||
impl i8/i16/i32/i64/i128/isize/u8/u16/u32/u64/u128/usize for i8/i16/i32/i64/i128/isize/u8/u16/u32/u64/u128/usize { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
impl i8/i16/i32/i64/i128/isize/u8/u16/u32/u64/u128/usize for i8/i16/i32/i64/i128/isize/u8/u16/u32/u64/u128/usize { | |
impl WrappingFrom<i8/i16/i32/i64/i128/isize/u8/u16/u32/u64/u128/usize> for i8/i16/i32/i64/i128/isize/u8/u16/u32/u64/u128/usize { |
|
||
## No *technical* symmetry requirement | ||
|
||
If there'a `A: WrappingFrom<B>`, then there *should* be an `B : WrappingFrom<A>` as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there'a `A: WrappingFrom<B>`, then there *should* be an `B : WrappingFrom<A>` as well. | |
If there's `A: WrappingFrom<B>`, then there *should* be a `B: WrappingFrom<A>` as well. |
Alternatively,
If there'a `A: WrappingFrom<B>`, then there *should* be an `B : WrappingFrom<A>` as well. | |
If `A: WrappingFrom<B>`, then `B` *should* implement `WrappingFrom<A>`. |
There could, for example, be some sort of `.truncate()` method similar to `.into()`, | ||
perhaps tweaked for better turbofishing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably, this would be via a WrappingInto
trait, not truncate
, and be wrapping_into
. I think that it's fine to exclude from the initial RFC, but this should probably be at least sort-of fleshed out here.
I see On the other hand, it is not clear from the proposal how signed to unsigned conversion (and vice versa) is handled. Will My understanding is that |
afaict
|
We have
From
for infallible,TryFrom
for checked, and this proposesWrappingFrom
for modular conversions.Rendered