1

I have a 5-bit u8, let's say 0b10101. I want to insert three bits, all ones, into positions 1, 2, and 4, to get: ii1i0101, i.e., 11110101. I want to accomplish this in three function calls, meaning that the function should take the index as one of the parameters and insert a single bit in that position.

I've come across this question, however, the answers on that page did not work for me. For example, the answer with the least upvotes panics when implemented, while others do not give the correct result.

fn insert_at(x: u8, index: u8, bit: u8) -> u8 {
    let mask = (1 << (8 - index + 1)) - 1;
    (x & !mask) | (bit << (8 - index)) | ((x & mask) >> 1)
}

#[cfg(test)]
mod tests {
    use super::*;
    use rstest::*;

    #[rstest(
        input, index, expected,
        case(0b10101, 1, 0b110101),
    )]
    fn test_bla(input: u8, index: u8, expected: u8) {
        let result = insert_at(input, index, 1);
        assert_eq!(result, expected);
    }
}

thread 'tests::test_bla::case_1' panicked at 'attempt to shift left with overflow' 
1
  • Do you only want to be able to insert a one or zero at a given index or will you only be changing bits to ones? Commented Jan 17, 2021 at 0:30

1 Answer 1

2

I've made a few assumptions (and a modification) to make the semantics of your question a bit more concrete:

  1. The result of the operation must fit in 8 bits; if it does not, no value is returned.
  2. index 0 (rather than index 1) refers to the position to the left of the most significant set bit.
  3. A bit inserted in any index > 0 shifts all more significant set bits to the left by 1.

A working implementation (playground link):

fn insert_at(x: u8, index: u8, bit: u8) -> Option<u8> {
    if bit != 0 && bit != 1 {
        return None;
    }
    
    // most significant set bit, from right
    // 0b00010101
    //   87654321
    let msb = 8 - x.leading_zeros() as u8;
    
    if index >= msb {
        // the new bit is past the LSB
        return None;
    }
    
    // an index of 0 means insert as the new MSB (if it fits).
    // insert is the absolute index of the inserted bit.
    let insert = msb - index;

    if insert == 8 {
        // the new bit is out of u8 range.
        // 0b_11111111
        //   ^ trying to insert here
        return None;
    }
    
    let mask = (1 << insert) - 1;
    Some((x & !mask) << 1 | (bit << insert) | (x & mask))
}

The reason your implementation panics is that Rust's left-shift operation is checked: if any bits would be shifted off the left side of the integer, the check fails. The main reasoning for this is that different platforms have different behavior in this case.

Rust also provides arithmetic operations with specified behavior in these cases, such as overflowing_shl() and wrapping_shl().

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.