As you may know, Swift is a type-safe language and good understanding of the data types that are available is therefore critically important. In this post, we’re going to start looking at these in detail with a look at the numeric data types available in Swit.
Table of Contents
Integers
Integers represent whole numbers, numbers that have no fractional components like 0
, 42
or -128
.
Signed and Unsigned Integers
In Swift, the integers are primarily divided into two groups. They can either be signed (which include negative values, zero and positive values) or unsigned (which covers only zero and positive values).
Sized Integers
In addition to being either signed or unsigned, in Swift each of these two groups also come in four different sizes; 8, 16, 32 and 64-bits versions.
In Swift, the names of the integer data types follow the same general scheme as is used in languages like C and as with all types in Swift, they always start with a capital letter.
For the unsigned types this initial capital letter is the letter U
(to indicate that they are unsigned). This is then followed by the word Int
(with a capital I
) to indicate that they are also part of the integer type family. In the case of the signed integer types the initial letter U
is omitted and they simply start with the word Int
. After the word Int
though, both the signed and unsigned integer types include a number to indicate how many bits of storage the particular type makes use of. This can be one of the aforementioned 8, 16, 32 or 64 to represent 8, 16, 32 or 64-bits respectively.
Putting all of this together then you can see that we have a whole range of integer types at our disposal. These include integer types such as UInt8
(an unsigned integer type using 8 bits of storage), Int16
(a signed integer, – notice it has no leading U
– using 16 bits of storage) or UInt64
(an unsigned integer type using 64-bits of storage). The full list is below:
- Int8
- Int16
- Int32
- Int64
- UInt8
- UInt16
- UInt32
- UInt64
Now although these types are useful, there are a lot of times when we don’t actually care about the number of bits of storage our integers are using. For these situations we make use of another, more generic, integer type built into Swift called Int
.
Int
The Int
type is the generic integer type of choice when writing Swift code and you should use it whenever possible. By using Int
consistently instead of the sized integer types we just looked at we can increase both the portability and readability of our code and can significantly reduce the need to convert between different data types.
Given it’s more generic nature, you may be wondering how much storage the Int
type actually uses to store its values and this is actually a good question. Under the hood the Int
type maps to “a type that is of the same size as the current platforms native word size, the smallest addressable unit of memory on the particular platform.”
Now that may be a bit of a mouthful but all it really means is that on a 64-bit platform the Int
type would be the equivalent size to an Int64
and on a 32-bit platform, it would be equivalent to an Int32
.
With the Int
type therefore using at least 32-bits of storage, for most situations the type provides more than enough storage space for the integer values you’ll normally be working. If however, you have specific requirements such as working with data from external data sources, are worried about performance or memory usage or are trying to perform other optimisations, you have the option of falling back to one of the sized integers such as UInt8
, UInt64
etc.
In addition to the Int
type though, Swift also contains an unsigned equivalent called UInt
.
UInt
In similar fashion to the Int
type, the UInt
also maps to the native word size on a given platform so on a 64-bit platform it is the same size as a UInt64
and on a 32-bit platform it is the same size as a UInt32
.
Despite the fact that the UInt
type is available within Swift though, Apple recommends that where possible, you use Int
in preference to the UInt
, even if the values that you are storing are unsigned.
Now, at first, this may seem strange, after all, why use a signed type even when you know the values you’re going to be storing are unsigned? But remember what I said about being consistent. By always using a single integer type whenever possible, we make our code easier to read, easier to move between platforms and also make sure that the values in our constants and variables are immediately interoperable without the need for any sort of conversion. It also has the advantage that we can take make use of the inbuilt type inference mechanism built into Swift which infers integers to be of type Int
by default.
Floating-Point Types
So, we’ve looked at the integer types, lets now take a look at their counterparts, the floating-point numbers.
Floating-point numbers are numbers that have a fractional component such as 3.14159
or 101.123
and as such are usually written using a decimal point.
Due to the way they are stored, variables and constants that are defined as floating-point numbers can store a much wider range of values than integers using the same number of bits of storage including values that are not only smaller, but also much larger than integers of the equivalent size. They do this though by lose some level of precision.
For the most part this loss of precision is not a major problem, especially when you stack this up against the greater flexibility that these numbers provide but you need to be aware that in the case of floating point numbers it can lead to some strange results such as when trying to test two floating point values for equality or trying to store a values that cannot be represented exactly using binary.
Float and Double
In Swift, there are two types of floating-point number, both of which are signed. These are the Float
type which represents 32-bit floating point numbers with at least 6 decimal digits of precision and theDouble
type which represents 64-bit floating point numbers at least 15 decimal digits of precision.
In similar fashion to the discussion we had about Int
and UInt
, in the interests of consistency, Apple recommend that we use the Double
type in preference to the Float
type in situations where both types are equally applicable.
Their reasoning is the same as we saw above (mainly readability, consistency and portability) and also ties into Swifts type inference mechanism, which in the case of floating-point numbers, infers values to be of type Double
by default.
Numeric Literal Values
So, we’ve looked at the two groups of numeric data types in Swift, the integer types and the floating-point types but how do we actually write values of these types in our code? This is where literal values come in.
Literals values are exactly as they sound, they are values that are literally written in our code verbatim and in Swift we have a number of options for how we can write them. Let’s look at writing integer literals first.
Integer Literals
In the case of the integer values, the first form we can use to write integer values in is good old decimal.
Decimal is the form you’ll be most familiar with where values are represented in a base-10 format without any sort of prefix or extra notation. For example:
let decimalInteger = 42
// 42 = (4 * 10) + (2 * 1)
On top of decimal notation though, there are three other notations that we can use to write integer values in Swift; binary, octal and hexadecimal. Let’s look at each of them in turn.
Binary Integer Literals
When we write an integer value in binary notation in Swift, we’re representing the value in a base-2 notation and write it with a leading zero, followed by a lowercase b
(for binary) followed by the value written in base-2.
For example, if I wanted to initialise a constant with the decimal value 42 written in binary notation I would write:
let binaryInteger = 0b101010 // The equivalent of decimal 42.
// 42 = (1 * 32) + (1 * 8) + (1 * 2)
Octal Integer Literals
The next option we have is octal notation. Octal notation represents values in a base-8 notation and to indicate the values is in octal notation we write a leading zero followed by a lowercase o
(for octal) followed by the value in base-8.
For example, if we wanted to initialise a constant with the octal equivalent of decimal 42 we would write:
let octalInteger = 0o52 // The equivalent of decimal 42.
// 42 = (5 * 8) + (2 * 1)
Hexadecimal Integer Literals
The final choice we have is hexadecimal notation. When we write a literal value in hexadecimal form we prefix the value with a zero followed by a lowercase x
(the x
from hexadecimal) followed by the number in hexadecimal.
So if we wanted to declare a final constant, again with the equivalent of the decimal value 42, but this time written in hexadecimal we would write :
let hexadecimalInteger = 0x2A // The equivalent of decimal 42.
// 42 = (2 * 16) + (10 * 1)
Floating-Point Literals
As with integer values, it is relatively common to write floating-point literals within our code and in the case of floating-point numbers we actually have slightly fewer choices when it comes to formats as we are limited to either decimal notation or hexadecimal notation.
Decimal Floating-Point Literals
The first notation, decimal, is the one you be most familiar with. As you know this format uses a decimal point to separate the integer part of the number from the fractional part. For example:
let decimalDouble = 3.14159 // Pi
One thing to note is that in Swift, you must always have a number on both sides of the decimal point so writing something like .5
in Swift is invalid.
Hexadecimal Floating-Point Literals
In addition to decimal notation, we can also write floating point numbers in Swift using hexadecimal notation. To write floating point numbers in hexadecimal notation you prefix the number with a zero, followed by a lower-case x
(0x
). When writing floating-point numbers in this way, both the whole number and fractional parts are both written in hexadecimal and are separated by a decimal point. For example, I could write pi
in the following format:
let hexadecimalDouble = 0x3.374F
Scientific Notation
Now, if you can remember back to school, when you were writing either very larger or very small numbers you may have used something called scientific notation.
Scientific notation is a kind of short hand, a more convenient way of writing floating point numbers using an optional exponent and given that I’ve mentioned it here, it won’t surprise you that we also have the option of using this notation in Swift.
To write decimal floating point literals in scientific notation we use an upper or lowercase letter e
to separate the base value from the exponent. The total value of the floating point number is then equivalent of multiplying the base value, (the part of the number before the e
), by 10 (in the case of decimal values) raised to the power of the exponent value (where the exponent value is the part of the number after the e
).
So if I wrote, the following: 2.56e2
it would be the equivalent of writing 2.56 * 10^2
or 256
.
Similarly, if I wrote: 2.56e-2
, (notice here that I used a negative exponent), it would be the equivalent of writing: 2.56 * 10^-2
or 0.0256
.
In Swift, we can also write floating point numbers in hexadecimal format and still make use of scientific notation.
When writing in hexadecimal format, instead of using an e
or E
to separate the base value from the exponent we , we use an upper or lowercase letter p
. When written in hexadecimal format, the literal value is equivalent to multiplying the base value (which is written in hexadecimal) by 2 raised to the power of the exponent (which is also written in hexadecimal).
So for example if we wrote the following floating point literal: 0xAp2
It would be the equivalent of writing: 10 * 2^2
or 40.0
. Notice how the number is still prefixed with the 0x
to indicate that is is a hexadecimal number.
Similarly if I wrote0xAp-2
It would be the equivalent of writing: 10 * 2^-2
or 2.5
.
Formatting for Numeric Literal Values
In addition to the different syntaxes that we can use to write numeric literal values, Swift also allows us to some syntactic sugar that helps us make those values easier to read. Firstly, both integers and floating point numbers can be written with additional leading zeros e.g.:
let pi = 00003.14159
They an also be written with underscores between group of digits to help with readability:
let largeInteger = 3_000_000_000
let largeDouble = 2_345_678.910_111_213
In both cases, the additional syntactic sugar has no effect on the underlying value that is represented, it is simply ignored by the compiler.
Integer Value Limits
Now, as I mentioned earlier, integer types in Swift use a fixed number of bits in which to store their values. In doing so, the number of bits they use, provides a direct limit on the range of values that variables or constants of that type can store.
For example, the Int8
type, a signed integer using 8 bits of storage can store values ranging from -128
to 127
whereas it’s unsigned equivalent (UInt8
) can store values that range from 0
through to 255
.
In both cases, if you attempt to store a value that does not fit within the range of values supported by the type, the compiler will indicate it as an error at the point you compile your code.
So given the fact that the integer values in Swift have a particular range of values that they can store, how do we find out how do we find out what this range of values is without doing some fancy binary maths?
Well, in Swift, built into each of the integer types are a couple of type properties that allow us to discover this information.
Value Ranges of Types
To find the smallest value a particular integer type can store we use the min
property and access it using dot notation.
Note: All the dot notation is, is using a dot or full stop to separate a item we want to know something about (in this case a type) from the information we want to know about it (in this case the minimum value it can store which is represented by the min
property).
For example if I wanted to access the minimum value that can be stored in say an Int8
I would write:
Int8.min // Returns -127
We can also access the maximum value that can be stored in a particular type. You can do this using the max
property. For example:
Int8.max // Returns 128
In both these cases, the values returned from these properties are of the same type as the type whose properties we’ve accessed so in the examples above the values returned from the min
and max
properties would both be of type Int8
. This allows us to easily use these returned values in calculations of that type without the need to convert them. With that said, though, the need to convert values between different types is not unusual in Swift, so we’ll take a look at that next.
Numeric Type Conversion
Converting Between Integer Types
As we saw just now, each of the integer value types in Swift have a different range of values that they can store and to convert a value from one data type to another Swift forces us to explicitly opt-in to the conversion on a case-by-case basis. By doing so, it helps avoid hidden conversion errors by making us indicate these conversions explicitly.
The mechanism for converting one type to another in Swift is simple. We simply create a new value of the desired type and initialise it with the existing value:
let daysInAYear : Int16 = 365
let daysInJanuary : Int8 = 31
let totalDays = daysInAYear + UInt16(daysInJanuary)
In this example, we initially create two constants, the first, daysInAYear
is of type Int16
and the second, the daysInJanuary
is of type UInt8
. In the last line, we then create a new constant (totalDays
) by combining the values held in the two previous constants. To do this though, the values that we are combining have to be of the same time so we first have to convert the daysInJanuary
constant into an Int16
to match the type of the daysInAYear
constant. To do this we create a new value of type UInt16
using initialisation syntax passing in the value from the daysInJanuary
constant. When then combine the new value with the “daysInAYearconstant to create the
totalDaysconstant which Swift infers to be of type
Int16`.
Integer Initialisers
The typeName(initialValue)
we used in the example above is an example of using the default UInt16
initializer and as you can see, we provided it with an initialisation value as part of that call (in this case the value held in the daysInJanuary
constant). Behind the scenes, the UInt16
type has a number of different initialisers, each of which accepts an initialisation parameter of a different type. In this case we make use of the initialiser that accepts a UInt8
parameter but there are others. It’s a similar story with the other numeric types in Swift. Each them has a specific set of initialisers, each tailored to accept initialisation parameters of specific types. This means that you can’t simply initialise numeric values with any old type. There are also some subtleties to these initialisations as well. For example, the UInt8
type has an initialiser that accepts a UInt16
parameter but if the value you provide doesn’t fit within the range of values supported by a UInt8
(0
to 255
), the compiler will, by default, give you an error.
let littleUInt16 = 120
let littleUInt8 : UInt8 = UInt8(littleUInt16)
let bigUInt16 = 1440
let bitUInt8 : UInt8 = UInt8(bigUInt16) // Compiler error.
Converting Between Integer and Floating Point Types
In addition to being able to convert between different integer types, Swift also allows us to convert between integer and floating-point types as well. As with the integer types, any conversion must be explicitly stated though:
let startingRatio = 1 // Inferred as an Int
let frationalRatio = 0.61803398875 // Inferred as a Double
let goldenRatio = Double(startingRatio) + fractionalRatio
// goldenRatio equals 1.61803398875 and is inferred to be of type Double.
As we saw with the integer example earlier, in order to be able to combine the two values, we must first ensure that they are all of the same type. To achieve this, we create a new Double
value using the value stored in the startingRatio
constant (in this case, we’re calling an initialiser on the Double
type that accepts an Int
as a parameter) and then add that to the existing fractionalRatio
constant. The result is the goldenRatio
constant and which is inferred to be of type Double
by Swift.
Converting Between Floating Point and Integer Types
In addition to being able to convert from integers to floating point numbers, we can also convert the other way, from floating point number to integers and as we’ve seen with all the conversions so far, we must explicitly opt-in to this. In the case of floating point to integer conversions, this is even more important than normal as when a floating point value is converted into an integer value in Swift, the fractional part of the floating point number is truncated:
let integerGoldenRatio = Int(goldenRatio)
// integerGoldenRatio is equal to 1 and is inferred to be of type Int
This applies for both Float
and Double
values.
Converting Numeric Literals
One thing to point out at this point is are the rules for converting numeric literals. When we include numeric literals in our code they aren’t actually typed until they are evaluated by the compiler. This means that any literal values you write in your code don’t have to be converted before they can be combined. For example, if we revisited our golden ratio example notice how in this example, we don’t need to convert the integer literal 1
before we combine it:
let otherGoldenRatio = 1 + fractionalRatio
Wrapping Up
So that about wraps it up for the numeric data types in Swift. As ever, if I’ve missed anything or you have an questions, get in touch.