Conv i8 -> u8, i16 -> u16

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?

1 Like

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 return 0
    • 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 to f32::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.

2 Likes

Casts between signed and unsigned integers of the same size are equivalent to a transmute and leaves the bit pattern unchanged.

1 Like
fn foo(x: i8) -> u8 {
  x.wrapping_sub(i8::MIN) as u8
}

fn foo2(x: i16) -> u16 {
  x.wrapping_sub(i16::MIN) as u16
}
6 Likes

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)

5 Likes

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.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.