Is there a more idiomatic way to express this encoding ?
fn foo(x: i8) -> u8 {
((x as i16) + 128) as u8
}
fn foo2(x: i16) -> u16 {
((x as i32) + (1 << 15)) as u16; // EDIT: typo, was 1<<16; should be 1<<15
}
Is there a more idiomatic way to express this encoding ?
fn foo(x: i8) -> u8 {
((x as i16) + 128) as u8
}
fn foo2(x: i16) -> u16 {
((x as i32) + (1 << 15)) as u16; // EDIT: typo, was 1<<16; should be 1<<15
}
the first one flips the highest bit, right? what does the second one do?
The second one is a typo, which is now fixed. Was 1<<16, is now 1<<15.
These are equivalent:
fn foo(x: i8) -> u8 {
(x as u8).wrapping_add(128)
}
fn foo2(x: i16) -> u16 {
(x as u16).wrapping_add(1 << 15)
}
Sorry, where is how (x as u8) documented? I thought (perhaps incorrectly) it rounded all neg to 0.
Numeric cast
Casting between two integers of the same size (e.g. i32 -> u32) is a no-op (Rust uses 2's complement for negative values of fixed integers)
Casting from a larger integer to a smaller integer (e.g. u32 -> u8) will truncate
Casting from a smaller integer to a larger integer (e.g. u8 -> u32) will
- zero-extend if the source is unsigned
- sign-extend if the source is signed
Casting from a float to an integer will round the float towards zero
NaN
will return0
- Values larger than the maximum integer value, including
INFINITY
, will saturate to the maximum value of the integer type.- Values smaller than the minimum integer value, including
NEG_INFINITY
, will saturate to the minimum value of the integer type.Casting from an integer to float will produce the closest possible float *
- if necessary, rounding is according to
roundTiesToEven
mode ***- on overflow, infinity (of the same sign as the input) is produced
- note: with the current set of numeric types, overflow can only happen on
u128 as f32
for values greater or equal tof32::MAX + (0.5 ULP)
Casting from an f32 to an f64 is perfect and lossless
Casting from an f64 to an f32 will produce the closest possible f32 **
- if necessary, rounding is according to
roundTiesToEven
mode ***- on overflow, infinity (of the same sign as the input) is produced
if integer-to-float casts with this rounding mode and overflow behavior are not supported natively by the hardware, these casts will likely be slower than expected.
** if f64-to-f32 casts with this rounding mode and overflow behavior are not supported natively by the hardware, these casts will likely be slower than expected.
*** as defined in IEEE 754-2008 §4.3.1: pick the nearest floating point number, preferring the one with an even least significant digit if exactly halfway between two floating point numbers.
Casts between signed and unsigned integers of the same size are equivalent to a transmute and leaves the bit pattern unchanged.
fn foo(x: i8) -> u8 {
x.wrapping_sub(i8::MIN) as u8
}
fn foo2(x: i16) -> u16 {
x.wrapping_sub(i16::MIN) as u16
}
Always good to see how LLVM doesn’t care how you implement stuff, anyways:
pub fn foo(x: i8) -> u8 {
((x as i16) + 128) as u8
}
pub fn foo1(x: i8) -> u8 {
x.wrapping_sub(i8::MIN) as u8
}
pub fn foo2(x: i8) -> u8 {
(x as u8) ^ (1 << (u8::BITS - 1))
}
playground::foo:
leal -128(%rdi), %eax
retq
.set playground::foo1, playground::foo
.set playground::foo2, playground::foo
(do compile to “ASM” in the playground in “Release” mode)
(all 3 get the same assembly, and then deduplicated)
However, I was surprised that LLVM wasn't able to optimize this:
fn foo(x: i32) -> u32 {
x.wrapping_sub(i32::MIN) as u32
}
pub fn subtract(a: i32, b: i32) -> u32 {
foo(a) - foo(b)
}
subtract
is equivalent to a - b
(in release mode) but LLVM doesn't see it.