Int to UInt (and vice versa) bit casting in Swift

20,834

Solution 1

You can do:

let unsigned = UInt8(bitPattern: Int8(-1)) // -> 255
let signed   = Int8(bitPattern: UInt8(0xff)) // -> -1

Many similar initializers exist:

extension Int8 {
    init(_ v: UInt8)
    init(_ v: UInt16)
    init(truncatingBitPattern: UInt16)
    init(_ v: Int16)
    init(truncatingBitPattern: Int16)
    init(_ v: UInt32)
    init(truncatingBitPattern: UInt32)
    init(_ v: Int32)
    init(truncatingBitPattern: Int32)
    init(_ v: UInt64)
    init(truncatingBitPattern: UInt64)
    init(_ v: Int64)
    init(truncatingBitPattern: Int64)
    init(_ v: UInt)
    init(truncatingBitPattern: UInt)
    init(_ v: Int)
    init(truncatingBitPattern: Int)
    init(bitPattern: UInt8)
}

Solution 2

I took the algebra route. Testing has been a pain because it is easy to get an overflow with the strong typing breaking the execution, PlayGround returned a negative value from the toUInt function, it kept crashing or gave funny errors doing a double casting (I opened a bug report). Anyway this is what I ended up with:

func toUint(signed: Int) -> UInt {

    let unsigned = signed >= 0 ?
        UInt(signed) :
        UInt(signed  - Int.min) + UInt(Int.max) + 1

    return unsigned
}

func toInt(unsigned: UInt) -> Int {

    let signed = (unsigned <= UInt(Int.max)) ?
        Int(unsigned) :
        Int(unsigned - UInt(Int.max) - 1) + Int.min

    return signed
}

I tested them with all extreme values (UInt.min, UInt.max, Int.min, Int.max) and when XCode doesn't go crazy it seems to work, but it looks overly complicated. Bizarre enough the UInt to Int bit casting could be simply achieved with the hashvalue property as in:

signed = UInt.max.hashValue // signed is -1

But obviously it isn't guaranteed to always work (it should, but I'd rather not taking the chance).

Any other idea will be appreciated.

Solution 3

numericCast(...) is what you're looking for. It's a set of generic functions that converts from and to different number types. It picks the correct implementation based on the types of its argument and the return type.

Share:
20,834

Related videos on Youtube

Michele Dall'Agata
Author by

Michele Dall'Agata

A former C programmer who moved into Unix System administration since many years. Fascinated by the beauty and power of the new Apple's programming language, Swift.

Updated on July 05, 2022

Comments

  • Michele Dall'Agata
    Michele Dall'Agata almost 2 years

    I am looking for a direct way to bit cast the bit values of an Int to UInt and vice versa. For example (using the 8 bits integers for simplicity) I want to achieve the following:

    let unsigned: UInt8 = toUInt8(-1)  // unsigned is 255 or 0xff
    let signed:   Int8  = toInt8(0xff) // signed is -1 
    

    At first I came out with the following solution:

    let unsigned = unsafeBitCast(Int8(-1), UInt8.self)
    let signed   = unsafeBitCast(UInt8(0xff), Int8.self)
    

    But Apple in the "unsafeBitCast()" documentation states the following:

    .. Caution:: Breaks the guarantees of Swift's type system; use with extreme care. There's almost always a better way to do anything.

    Does anyone have the better way?

    • Martin R
      Martin R over 9 years
      Similar question here: Converting signed to unsigned in Swift.
    • Michele Dall'Agata
      Michele Dall'Agata over 9 years
      @Martin yes, you're right. I swear I have been searching for this in both google and stackoveflow, it never appeared.
  • Jens Schwarzer
    Jens Schwarzer over 7 years
    Compared to UInt8(bitpattern:) then numericCast traps on under/overflow, e.g., let foo: UInt8 = numericCast(-1)will crash. So choosing the right one depends on the use-case. :)