Reputation: 13
What is the rusticle way to to represent the an i64 [-9223372036854775808, 9223372036854775807] into the u64 domain [0, 18446744073709551615]. So for example 0 in i64 is 9223372036854775808 in u64.
Here is what I have done.
let x: i64 = -10;
let x_transform = ((x as u64) ^ (1 << 63)) & (1 << 63) | (x as u64 & (u64::MAX >> 1));
let x_original = ((x_transform as i64) ^ (1 << 63)) & (1 << 63) | (x_transform & (u64::MAX >> 1)) as i64;
println!("x_transform {}", x_transform);
println!("x_original {} {}", x_original, x_original == x);
yielding
x_transform 9223372036854775798
x_original -10 true
Is there a built in way to do this, because it seems too verbose, and error prone?
Upvotes: 1
Views: 3663
Reputation: 42472
The simplest solution would be to just translate the two's complement representation, rather than use offset-binary:
let x_transform = u64::from_ne_bytes(x.to_ne_bytes());
let x_original = i64::from_ne_bytes(x_transform.to_ne_bytes());
However according to the to the wiki:
Offset binary may be converted into two's complement by inverting the most significant bit.
So you could do that and use the less error-prone two's complement for the actual translation:
pub fn convert1(x: i64) -> u64 {
((x as u64) ^ (1 << 63)) & (1 << 63) | (x as u64 & (u64::MAX >> 1))
}
pub fn convert3(x: i64) -> u64 {
// manipulating the bytes in transit requires
// knowing the MSB, use LE as that's the most
// commmon by far
let mut bytes = x.to_le_bytes();
bytes[7] ^= 0x80;
u64::from_le_bytes(bytes)
}
pub fn convert4(x: i64) -> u64 {
u64::from_ne_bytes((x ^ i64::MIN).to_ne_bytes())
}
all produce the exact same x86_64 code:
movabs rax, -9223372036854775808
xor rax, rdi
ret
Upvotes: 1
Reputation: 27533
From a performance view it doesn't really matter how you write it, a quick check on godbot shows both the wrapping and the bit shifty versions compile to the same machine code. But I'd argue the variants with wrapping are way more readable and convey the intent better.
pub fn wrap_to_u64(x: i64) -> u64 {
(x as u64).wrapping_add(u64::MAX/2 + 1)
}
pub fn wrap_to_i64(x: u64) -> i64 {
x.wrapping_sub(u64::MAX/2 + 1) as i64
}
pub fn to_u64(x: i64) -> u64 {
((x as u64) ^ (1 << 63)) & (1 << 63) | (x as u64 & (u64::MAX >> 1))
}
pub fn to_i64(x: u64) -> i64 {
((x as i64) ^ (1 << 63)) & (1 << 63) | (x & (u64::MAX >> 1)) as i64
}
example::wrap_to_u64:
movabs rax, -9223372036854775808
xor rax, rdi
ret
example::wrap_to_i64:
movabs rax, -9223372036854775808
xor rax, rdi
ret
example::to_u64:
movabs rax, -9223372036854775808
xor rax, rdi
ret
example::to_i64:
movabs rax, -9223372036854775808
xor rax, rdi
ret
The lesson to learn is that unless you have a very specific optimization probably the compiler will outperform you.
Upvotes: 2