Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unsigned right shift operator '>>>' in Swift

How would you implement the equivalent to Java's unsigned right shift operator in Swift?

According to Java's documentation, the unsigned right shift operator ">>>" shifts a zero into the leftmost position, while the leftmost position after ">>" depends on sign extension.

So, for instance,

    long s1 = (-7L >>> 16); // result is 281474976710655L
    long s2 = (-7L >> 16); // result is -1

In order to implement this in Swift, I would take all the bits except the sign bit by doing something like,

    let lsb = Int64.max + negativeNumber + 1

Notice that the number has to be negative! If you overflow the shift operator, the app crashes with EXC_BAD_INSTRUCTION, which it's not very nice... Also, I'm using Int64 on purpose. Because there's no bigger datatype, doing something like (1 << 63) would overflow the Int64 and also crash. So instead of doing ((1 << 63) - 1 + negativeNumber) in a bigger datatype, I wrote it as Int64.max + negativeNumber - 1.

Then, shift that positive number with the normal logical shift, and OR the bit from the sign in the first left bit after the sign.

    let shifted = (lsb >> bits) | 0x4000000000000000

However, that doesn't give me the expected result,

    ((Int64.max - 7 + 1) >> 16) | 0x4000000000000000 // = 4611826755915743231

Not sure what I'm doing wrong... Also, would it be possible to name this operator '>>>' and extend Int64?

Edit: Adding here the solution from OOper below,

infix operator >>> : BitwiseShiftPrecedence

func >>> (lhs: Int64, rhs: Int64) -> Int64 {
  return Int64(bitPattern: UInt64(bitPattern: lhs) >> UInt64(rhs))
}

I was implementing the Java Random class in Swift, which also involves truncating 64-bit ints into 32-bit. Thanks to OOper I just realized I can use the truncatingBitPattern initializer to avoid overflow exceptions. The function 'next' as described here becomes this in Swift,

var seed: Int64 = 0
private func next(_ bits: Int32) -> Int32 {
    seed = (seed &* 0x5DEECE66D &+ 0xB) & ((1 << 48) - 1)
    let shifted : Int64 = seed >>> (48 - Int64(bits))
    return Int32(truncatingBitPattern: shifted)
}
like image 583
endavid Avatar asked Jan 04 '23 22:01

endavid


1 Answers

One sure way to do it is using the unsigned shift operation of unsigned integer type:

infix operator >>> : BitwiseShiftPrecedence

func >>> (lhs: Int64, rhs: Int64) -> Int64 {
    return Int64(bitPattern: UInt64(bitPattern: lhs) >> UInt64(rhs))
}

print(-7 >>> 16) //->281474976710655

(Using -7 for testing with bit count 16 does not seem to be a good example, it loses all significant bits with 16-bit right shift.)

If you want to do it in your way, the bitwise-ORed missing sign bit cannot be a constant 0x4000000000000000. It needs to be 0x8000_0000_0000_0000 (this constant overflows in Swift Int64) when bit count == 0, and needs to be logically shifted with the same bits.

So, you need to write something like this:

infix operator >>>> : BitwiseShiftPrecedence

func >>>> (lhs: Int64, rhs: Int64) -> Int64 {
    if lhs >= 0 {
        return lhs >> rhs
    } else {
        return (Int64.max + lhs + 1) >> rhs | (1 << (63-rhs))
    }
}

print(-7 >>>> 16) //->281474976710655

It seems far easier to work with unsigned integer types when you need unsigned shift operation.

like image 200
OOPer Avatar answered Jan 08 '23 21:01

OOPer