Safely down casting from Int to Int8 in Swift Safely down casting from Int to Int8 in Swift json json

Safely down casting from Int to Int8 in Swift


Here are some options for converting your Int (which is coming from an NSNumber) into an Int8:

Just converting with Int8's initializer init(_: Int)

If your Int values are guaranteed to fit into an Int8, then converting them with Int8(value) is fine.

If you get an Int that doesn't fit in an Int8, your program will crash:

let i = 300let j = Int8(i)  // crash!

Initializing with Int8's initializer init(truncatingIfNeeded: BinaryInteger)

For a bit of extra safety, you should use the init(truncatingIfNeeded: BinaryInteger) initializer:

let i = 300let j = Int8(truncatingIfNeeded: i)  // j = 44

This does generate altered values which might not be desirable, but it would prevent a crash.

Explicitly checking the range of valid values

As another alternative, you could just check the range explicitly:

if (-10...10).contains(i) {    j = Int8(i)} else {    // do something with the error case}

The advantage of checking is that you can specify the valid range instead of just detecting an error when the range exceeds the values that will fit in an Int8.

Initializing with Int8's initializer init(exactly: Int)

Your code is currently using this method of safely initializing the Int8 values. This is robust since it will return nil if the value does not fit in an Int8. Thus it can checked with optional binding as you are doing, or it can be combined with the nil coalescing operator ?? to provide default values if you have appropriate ones:

// Determine Int8 value, use 0 if value would overflow an Int8let j = Int8(exactly: i) ?? 0

Directly casting NSNumber with as Int8 in Swift 3.0.1 and above

As @Hamish and @OOPer mentioned in the comments, it is now possible to cast an NSNumber directly to Int8.

let i: NSNumber = 300let j = i as Int8  // j = 44

This does have the same truncating effect as using init(truncatingIfNeeded: BinaryInteger).

In conclusion, your current code is safe, and probably is the best solution for your problem unless you would like to detect when the values exceed the current desired range of -10...10, in which case explicitly checking would be the better option.


As said @vacawama

let j = Int8(exactly: i) ?? 0 

is a perfect and safe way to convert Int to UInt8, but sometimes I need to get max and min values of UInt8 instead of zero. I'm using extension:

extension UInt8 {    /// Safely converts Int to UInt8, truncate remains that do not fit in UInt8.    /// For instance, if Int value is 300, UInt8 will be 255, or if Int value is -100, UInt8 value will be 0    init(truncateToFit int: Int) {        switch int {        case _ where int < UInt8.min: self.init(UInt8.min)        case _ where int > UInt8.max: self.init(UInt8.max)        default: self.init(int)        }    }    /// Safely converts Float to UInt8, truncate remains that do not fit in UInt8.    /// For instance, if Float value is 300.934, UInt8 will be 255, or if Float value is -100.2342, UInt8 value will be 0    init(truncateToFit float: Float) {        switch float {        case _ where float < Float(UInt8.min): self.init(UInt8.min)        case _ where float > Float(UInt8.max): self.init(UInt8.max)        default: self.init(float)        }    }    /// Safely converts Double to UInt8, truncate remains that do not fit in UInt8.    /// For instance, if Double value is 300.934, UInt8 will be 255, or if Double value is -100.2342, UInt8 value will be 0    init(truncateToFit double: Double) {        switch double {        case _ where double < Double(UInt8.min): self.init(UInt8.min)        case _ where double > Double(UInt8.max): self.init(UInt8.max)        default: self.init(double)        }    }}

For instance:

    let value = UInt8(truncateToFit: Int.max) // value == 255

UPDATE:

I'm found standard realization for all numbers conform to BinaryInteger protocol, such that Int, Int8, Int16, Int32, and so on.

let value = UInt8(clamping: 500) // value == 255let secondValue = UInt8(clamping: -500) // secondValue == 0

But for Double and Float, there is elegance solution, one extension for all BinaryInteger types

extension BinaryInteger where Self: FixedWidthInteger {    /// Safely converts Float to BinaryInteger (Uint8, Uint16, Int8, and so on), truncate remains that do not fit in the instance of BinaryInteger range value.    /// For instance, if Float value is 300.934, and self is UInt8, it will be 255, or if Float value is -100.2342, self value will be 0    init(truncateToFit float: Float) {        switch float {        case _ where float < Float(Self.min): self.init(Self.min)        case _ where float > Float(Self.max): self.init(Self.max)        default: self.init(float)        }    }    /// Safely converts Double to BinaryInteger (Uint8, Uint16, Int8, and so on), truncate remains that do not fit in the instance of BinaryInteger range value.    /// For instance, if Double value is 300.934, and self is UInt8, it will be 255, or if Float value is -100.2342, self value will be 0    init(truncateToFit double: Double) {        switch double {        case _ where double < Double(Self.min): self.init(Self.min)        case _ where double > Double(Self.max): self.init(Self.max)        default: self.init(double)        }    }}

For instance:

let valueUInt16 = UInt16(truncateToFit: 5000000.0) // valueUInt16 == 65535let valueInt8 = Int8(truncateToFit: 5000000.0)  // valueInt8 == 127let valueUInt8 = UInt8(truncateToFit: -500.0)   // valueUInt8 == 0