Skip to content

3. Numbers & math

What & why

Arithmetic is easy to write and surprisingly easy to get wrong: integer division truncates, floating point is inexact, and overflow can crash. Swift's choices here favour loud failure over silent wrong answers.

Basic operators

let a = 17
let b = 5

print(a + b)    // 22
print(a - b)    // 12
print(a * b)    // 85
print(a / b)    // 3   ← integer division drops the remainder
print(a % b)    // 2   ← remainder

With Doubles, / is real division:

print(17.0 / 5.0)     // 3.4
print(Double(a) / Double(b))   // 3.4

Compound assignment: += -= *= /= %=. Swift has no ++ or --; write count += 1.

Remainder for floating point is truncatingRemainder(dividingBy:):

print(7.5.truncatingRemainder(dividingBy: 2))   // 1.5

Operator precedence

Multiplication, division and remainder bind tighter than addition and subtraction. Use parentheses whenever it helps a reader:

print(2 + 3 * 4)       // 14
print((2 + 3) * 4)     // 20

Overflow traps — on purpose

An Int has a fixed range. Going outside it crashes instead of wrapping silently:

print(Int.max)          // 9223372036854775807
print(Int.min)
// let boom = Int.max + 1   // fatal error: arithmetic operation overflowed

If you actually want wraparound (hashing, checksums), use the overflow operators &+ &- &*:

let wrapped = Int.max &+ 1      // Int.min

Or detect it:

let (result, didOverflow) = Int.max.addingReportingOverflow(1)
print(result, didOverflow)      // -9223372036854775808 true

Division by zero for integers also traps. Check first.

Floating point is approximate

print(0.1 + 0.2)               // 0.30000000000000004
print(0.1 + 0.2 == 0.3)        // false

This is how binary floating point works in every language. Compare with a tolerance, or avoid == on computed doubles:

func nearlyEqual(_ x: Double, _ y: Double, tolerance: Double = 1e-9) -> Bool {
    abs(x - y) <= tolerance
}
print(nearlyEqual(0.1 + 0.2, 0.3))   // true

For money, store integer cents (or use Decimal from Foundation).

Special values: Double.infinity, -Double.infinity, and Double.nan ("not a number"). NaN != NaN; test with .isNaN.

Math functions

Import Foundation for the usual math library (it's available on all platforms):

import Foundation

print(sqrt(16.0))            // 4.0
print(pow(2.0, 10.0))        // 1024.0
print(sin(Double.pi / 2))    // 1.0
print(log(M_E))              // 1.0   (natural log)
print(abs(-5), max(3, 9), min(3, 9))   // 5 9 3

Rounding:

let x = 2.5
print(x.rounded())                       // 3.0   (to nearest, ties away from zero)
print(x.rounded(.down))                  // 2.0   (floor)
print(x.rounded(.up))                    // 3.0   (ceiling)
print(x.rounded(.towardZero))            // 2.0   (trunc)
print(x.rounded(.toNearestOrEven))       // 2.0   (banker's rounding)
print(Int(x.rounded()))                  // 3

abs, min, and max work on any integer or floating-point type without Foundation.

Converting numbers

let i = 7
let d = Double(i) / 2       // 3.5

let big: Int = 300
let small = UInt8(truncatingIfNeeded: big)   // 44 — keeps the low bits
// let crash = UInt8(big)                    // fatal error: not representable
let safe = UInt8(exactly: big)               // nil — fails gracefully

init(exactly:) returns an optional and is the safest way to convert when the value might not fit.

Random numbers

let dice = Int.random(in: 1...6)
let chance = Double.random(in: 0..<1)
let coin = Bool.random()

let colors = ["red", "green", "blue"]
print(colors.randomElement()!)    // random element (optional – `!` is explained later)
print(colors.shuffled())

These use a secure, system-provided generator by default. For reproducible sequences in tests, you can supply your own RandomNumberGenerator.

Formatting numbers for display

import Foundation

let price = 1234.5678
print(price.formatted())                                   // 1,234.568 (locale dependent)
print(price.formatted(.number.precision(.fractionLength(2))))   // 1,234.57
print(String(format: "%.2f", price))                       // 1234.57
print(0.256.formatted(.percent))                           // 26%

String(format:) uses C-style format strings; .formatted(...) is the modern, locale-aware way.

Common mistakes

  • Integer division surprise: 1 / 2 is 0. Convert one side to Double.
  • Comparing doubles with == after arithmetic.
  • Letting Int overflow unnoticed in a loop — it will crash, which is at least visible. Use &+ only when you truly want wraparound.
  • Int(someDouble) on NaN or huge values crashes. Check isFinite first or use Int(exactly:).
  • Forgetting import Foundation for sqrt, pow, sin, etc. (Linux/Windows especially).

Exercises

  1. Compute the average of [3, 8, 5] as a Double — without hard-coding the answer.
  2. Write code that converts Celsius to Fahrenheit: F = C * 9 / 5 + 32. Test with 100 and -40. Careful with integer division!
  3. Print the result of Int.max &+ 1 and then, in separate runs, Int.max + 1. Compare behaviour.
  4. Roll two dice 1,000 times and print how many times the sum was 7.
  5. Given a radius, print the area of a circle rounded to 2 decimal places.
  6. Write isPrime(_ n: Int) -> Bool (you may use a loop from the next lesson) and test it on a few numbers.

Recap

  • / on Ints truncates; % is the remainder; no ++/--.
  • Int overflow and division by zero trap; &+ &- &* wrap deliberately.
  • Floating point is approximate — compare with a tolerance.
  • import Foundation for sqrt, pow, trig, etc.
  • Convert explicitly; init(exactly:) is the safe form.

Next: Strings →