Swift Integer Quick Guide

If you are new to Swift and have some experience with “C” style languages you probably have not given the Swift integer types much thought. They mostly work as you expect until one day something catches you out.

There were a number of operators such as the overflow operators &+, &- and &*, dealing with exact bit patterns and failable numeric intializers that were new to me. So here is my quick guide to Swift integers.

Last Updated: Jan 16, 2020

Integer Types

Let’s get the basics out of the way. Swift has the familiar C-style set of types for signed and unsigned types:

Int8, UInt8    // 8-bit
Int16, UInt16  // 16-bit
Int32, UInt32  // 32-bit
Int64, UInt64  // 64-bit
Int, UInt      // Platform dependent (64-bit on modern devices)

Unless you have a reason to care use Int.

let someInt = 42           // Int
let int8 = Int8(127)       // 8-bit signed integer
let unit8 = UInt8(255)     // 8-bit unsigned integer

You can get the min and max value of each type:

Int8.min       // -128
Int8.max       //  127
Int32.min      // -2147483648
Int32.max      //  2147483647
UInt64.min     //  0
UInt64.max     //  18446744073709551615

Integer Literals

You are not restricted to decimal for integer literals. You also have binary (0b), octal (0o) and hexadecimal (0x):

let decimal = 42        // 42
let binary  = 0b101010  // 42
let octal   = 0o52      // 42
let hex     = 0x2A      // 42

Add leading zeroes and _ separators to improve readability:

let hex2bytes = 0x00_ff
let downloads = 1_000_000_000

Initialization

The usual initializers for Swift types:

let int8 = Int8(127)       // 8-bit signed integer
let unit8 = UInt8(255)     // 8-bit unsigned integer

Be careful, initializing with a value too large to fit is an error:

let tooBig = Int8(128)     // Integer overflow

Initializing with a String can also fail so returns an Optional:

let zip = Int("95014")             // 95014
let badzip = Int("XYZ 30")         // nil
let rad16 = Int("FF", radix: 16)   // 255
let bitrot = Int8("020", radix: 2) // nil

You truncate a Float or Double when converting to an integer

let pi = Int(3.14)   // 3

Creating an integer from a big endian byte order (long time since I needed that):

let fromBigEnd = Int.init(bigEndian: 0x4000_0000_0000_0000) // 0x40
let fromLittleEnd = Int.init(littleEndian: 0x40)            // 0x40

Swapping bytes:

let aa:UInt16 = 0x00AA
let swapped = aa.byteSwapped // 0xAA00

If you need to maintain an exact bit pattern when converting between signed and unsigned integers:

let bits: UInt8 = 0b1000_0000        //  128
// let topBit = Int8(bits)           //  error - overflows
let topBit = Int8(bitPattern: bits)  // -128 = 0b10000000
let back = UInt8(bitPattern: topBit) //  128 = 0b10000000

To truncate a bit pattern keeping the least significant bits that fit:

let full16 = 0xAA05
let lower8 = UInt8(truncatingIfNeeded: full16) // 5

Conversion Between Types

Using different number types for an operation gives an error. You must convert to a single type:

let height = Int8(5)
let width = 10
let area = height * width       // error Int8 * Int
let area = Int(height) * width  // 50 Int * Int

Trying to convert to a type with a value that does not fit gives an error:

let h = UInt8(25)     // UInt8(25)
let x = 10 * h        // UInt8(250)
// let y = 100 * h    // error - too big for UInt8
let y = 100 * Int(h)  // Int(2500)

Failable Initializers

It is an error to initialize an integer type with a value that does not fit:

let tooBig = Int8(128)  // Error, Int8.max is 127

To avoid this type of error, Swift 3.1 introduced failable numeric initializers that return an optional:

let input = 128
let tooBig = Int8(exactly: input)  // nil
let fits = Int16(exactly: input)   // 128

Overflow Operators

Swift treats integer overflow or underflow as an error when using the standard operators.

let maxInt32 = Int32.max  // 2147483647
let minInt32 = Int32.min  // -2147483648

let moreThan32 = maxInt32 + 1  // error
let lessThan32 = minInt32 - 1  // error
let twiceAsBig = maxInt32 * 2  // error

To remove the error and have integers wrap use the overflow operators (&+, &-, &*):

let overflowAdd = maxInt32 &+ 1  // -2147483648
let overflowSub = minInt32 &- 1  //  2147483647
let overflowMult = maxInt32 &* 2 // -2

If you want to know when an overflow happens:

let wrapAdd = maxInt32.addingReportingOverflow(maxInt32)        // (-2, true)
let wrapSub = Int8(-127).subtractingReportingOverflow(2)        // (127, true)
let wrapMult = UInt8(128).multipliedReportingOverflow(by: 2)    // (0, true)
let remainder = 127.remainderReportingOverflow(dividingBy: 10)  // (7, false)

These methods return a tuple with the result and a boolean flag that is true if the result overflowed.

At time of writing a compiler bug SR-5964 means that the dividedReportingOverflow may fail to compile when it detects an overflow will happen:

// https://bugs.swift.org/browse/SR-5964
let bug = Int.divideWithOverflow(Int.min, -1) // error

Bitwise Operators

Swift follows the C-style of bitwise operators for NOT (~), AND (&), OR (|), XOR (^):

let byte:UInt8 = 0b1010_0000     // 160 (0b1010_0000)
let mask:UInt8 = 0b0011_0011     //  51 (0b0011_0011)

let notByte = ~byte              //  95 (0b0101_1111)
let andByte = byte & mask        //  32 (0b0010_0000)
let orByte = byte | mask         // 179 (0b1011_0011)
let xorByte = byte ^ mask        // 147 (0b1001_0011)

Using the bit shifting operators << and >> with unsigned integers discards bits shifted beyond the bounds, empty bits are filled with zeroes:

let pattern:UInt8 = 0b1100_0011
pattern << 2     // 0b0000_1100
pattern >> 2     // 0b0011_0000

You can rotate an unsigned integer by OR’ing the results of a left shift and right shift:

// 8-bits rotated by 2 bits
let rotateLeft = pattern << 2 | pattern >> 6  // 0b0000_1111
let rotateRight = pattern << 6 | pattern >> 2 // 0b1111_0000

When bit shifting signed integers to the right, empty bits on the left are filled with the sign bit.

For example with an 8-bit integer the leading bit is the sign bit leaving 7-bits for the value. You store negative values as 2’s complement which means subtracting the value from 2^7 (128) and setting the sign bit:

// -10 => 128 - 10 = 118 = 0b111_0110 
let minus10 = Int8(bitPattern: 0b1_111_0110) // -10
minus10 << 2 // -40 => 128 - 40 = 88 = 0b101_1000
minus10 >> 2 // -3  => 128 - 3 = 125 = 0b111_1101

let minus40 = Int8(bitPattern: 0b1_101_1000) // -40
let minus3  = Int8(bitPattern: 0b1_111_1101) // -3

Further Reading

A playground with the code snippets from this post:

Since Swift 4. integers are protocol-oriented: