/Guide-to-Numbers-Sample-Code

Xcode Playground Sample Code for the Flight School Guide to Numbers

Primary LanguageSwiftMIT LicenseMIT

Flight School Guide to Swift Numbers Sample Code

This repository contains sample code used in the Flight School Guide to Swift Numbers.


Chapter 1

Chapter 1 is a conceptual deep-dive into how numbers work on computers in general and in Swift specifically.

Floating Point Approximates

You know how floating-point arithmetic sometimes produces unexpected results, like 0.1 + 0.2 != 0.3? (If not, go ahead and read the first chapter for free!)

This sample code implements an "approximately equals" operator (==~) for floating-point number types.

0.1 + 0.2 == 0.3 // false
0.1 + 0.2 ==~ 0.3 // true

(0.1 + 0.2).isApproximatelyEqual(to: 0.3, within: .ulpOfOne) // true

Floating Point Environment

Normally, you can't tell the difference between nan and signalingNaN. That's because Swift doesn't expose the floating-point environment in its standard library.

We can still access it from Darwin, though. And that's what this playground demonstrates:

do {
    try detectingFloatingPointErrors(flags: .invalid) {
        Double.signalingNaN + 1
    }
} catch {
    print("Error: \(error)")
}

Chapter 2

Chapter 2 is all about number formatting. The sample code in this chapter offers a comprehensive survey of the various formatting styles of NumberFormatter, and how they work in different locales.

Cardinal Numbers

let formatter = NumberFormatter()
formatter.string(for: 4) // 4

formatter.numberStyle = .spellOut
formatter.string(for: 4) // four

Ordinal Numbers

let formatter = NumberFormatter()
formatter.numberStyle = .ordinal
formatter.string(for: 1) // 1st

Decimal Numbers

let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.string(for: 1234567.89) 1,234,567.89

Significant Digits

let formatter = NumberFormatter()
formatter.usesSignificantDigits = true

formatter.maximumSignificantDigits = 2

formatter.string(from: 123) // 120
formatter.string(from: 123456) // 120000
formatter.string(from: 123.456) // 120
formatter.string(from: 1.230000) // 1.2
formatter.string(from: 0.00123) // 0.0012

Integer and Fraction Digits

let formatter = NumberFormatter()
formatter.usesSignificantDigits = false // default

formatter.minimumIntegerDigits = 4
formatter.minimumFractionDigits = 2

formatter.string(from: 123) // 0123.00
formatter.string(from: 123456) // 123456.00
formatter.string(from: 123.456) // 0123.46
formatter.string(from: 1.230000) // 0001.23
formatter.string(from: 0.00123) // 0000.00

Rounding Modes

let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 1

let numbers = [1.2, 1.22, 1.25, 1.27, -1.25]
let modes: [NumberFormatter.RoundingMode] = [.ceiling, .floor, .up, .down, .halfUp, .halfDown, .halfEven]

for mode in modes {
    formatter.roundingMode = mode

    for number in numbers {
        formatter.string(for: number)
    }
}

Scientific Notation

let formatter = NumberFormatter()
formatter.numberStyle = .scientific
formatter.string(for: 12345.6789) // 1.23456789E4

Percentages

let formatter = NumberFormatter()
formatter.numberStyle = .percent
formatter.string(for: 0.12) // 12%

Currencies

let formatter = NumberFormatter()

let identifiers =
    ["en-US", "en-GB", "de-DE", "ja-JP"]
let styles: [NumberFormatter.Style] =
    [.currency, .currencyAccounting, .currencyISOCode, .currencyPlural]

for style in styles {
    formatter.numberStyle = style
    for identifier in identifiers {
        formatter.locale = Locale(identifier: identifier)
        formatter.string(for: 1234.567)
    }
}

Custom Formats

let formatter = NumberFormatter()
formatter.numberStyle = .decimal

// Format with thousands and decimal separator
// that rounds to the nearest five tenths
formatter.format = "#,##0.5"

formatter.locale = Locale(identifier: "en-US")
formatter.string(for: 1234.567) // 1,234.5

formatter.locale = Locale(identifier: "fr-FR")
formatter.string(for: 1234.567) // 1 234,5

Chapter 3

Chapter 3 shows the correct way to represent and work with money in code.

Money

let prices: [Money<USD>] = [2.19, 5.39, 20.99, 2.99, 1.99, 1.99, 0.99]
let subtotal = prices.reduce(0.00, +)
let tax = 0.08 * subtotal
let total = subtotal + tax // $39.45

Currency Converter

let EURtoUSD = CurrencyPair<EUR, USD>(rate: 1.17) // as of June 1st, 2018

let euroAmount: Money<EUR> = 123.45
let dollarAmount = EURtoUSD.convert(euroAmount) // $144.44

Chapter 4

Chapter 4 covers Foundation's units and measurements APIs.

Natural Scale

let lengthOfRoom = Measurement<UnitLength>(value: 8, unit: .meters)
let distanceToAirport = Measurement<UnitLength>(value: 16, unit: .kilometers)

let formatter = MeasurementFormatter()
formatter.unitOptions = .naturalScale
formatter.string(from: lengthOfRoom) // 26.247 ft
formatter.string(from: distanceToAirport) // 9.942 mi

Provided Units

let ingotMass = Measurement<UnitMass>(value: 400, unit: .ouncesTroy)

let formatter = MeasurementFormatter()
formatter.unitOptions = .providedUnit
formatter.string(from: ingotMass) // 400 oz t

Configuring Precision

let barometerReading = Measurement<UnitPressure>(value: 29.9, unit: .inchesOfMercury)
let pressureInMillibars = barometerReading.converted(to: .millibars)

let formatter = MeasurementFormatter()
formatter.unitOptions = .providedUnit
formatter.numberFormatter.usesSignificantDigits = true
formatter.numberFormatter.maximumSignificantDigits = 3
formatter.string(from: pressureInMillibars) // 1,010 mbar

Temperature Formatting

let temperatureInF = Measurement<UnitTemperature>(value: 72, unit: .fahrenheit)
let temperatureInC = Measurement<UnitTemperature>(value: 20.5, unit: .celsius)

let formatter = MeasurementFormatter()
formatter.locale = Locale(identifier: "en-US")
formatter.string(from: temperatureInF) // 72°F
formatter.string(from: temperatureInC) // 68.9°F

formatter.locale = Locale(identifier: "fr-FR")
formatter.string(from: temperatureInF) // 22,222 °C
formatter.string(from: temperatureInC) // 20,5 °C

formatter.unitOptions = .temperatureWithoutUnit

formatter.locale = Locale(identifier: "en-US")
formatter.string(from: temperatureInF) // 72°
formatter.string(from: temperatureInC) // 20.5° (!)

formatter.locale = Locale(identifier: "fr-FR")
formatter.string(from: temperatureInF) // 72° (!)
formatter.string(from: temperatureInC) // 20,5°

Units Interoperability

let loggedFlyingTime =
    Measurement<UnitDuration>(value: 220, unit: .hours)

let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.day]
formatter.unitsStyle = .full
formatter.includesApproximationPhrase = true
formatter.string(from: loggedFlyingTime) // About 9 days

Chapter 5

Chapter 5 extends what we learned about units in the previous chapter to transform Xcode Playgrounds into an interactive physical calculator.

Agricultural Flight Planner

let takeoffWeight = (
    emptyPlaneWeight +
    payloadWeight +
    fuelWeight +
    pilotWeight
).converted(to: .pounds)

let canTakeOff = takeoffWeight < maximumTakeoffWeight // ???

License

MIT

About Flight School

Flight School is a new book series for Swift developers. Each month, we'll explore an essential part of iOS, macOS, and Swift development through concise, focused books.

If you'd like to get in touch, feel free to message us on Twitter (@flightdotschool) or email us at mailto:info@flight.school.