This repository contains sample code used in the Flight School Guide to Swift Numbers.
Chapter 1 is a conceptual deep-dive into how numbers work on computers in general and in Swift specifically.
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
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 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.
let formatter = NumberFormatter()
formatter.string(for: 4) // 4
formatter.numberStyle = .spellOut
formatter.string(for: 4) // four
let formatter = NumberFormatter()
formatter.numberStyle = .ordinal
formatter.string(for: 1) // 1st
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.string(for: 1234567.89) 1,234,567.89
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
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
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)
}
}
let formatter = NumberFormatter()
formatter.numberStyle = .scientific
formatter.string(for: 12345.6789) // 1.23456789E4
let formatter = NumberFormatter()
formatter.numberStyle = .percent
formatter.string(for: 0.12) // 12%
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)
}
}
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 shows the correct way to represent and work with money in code.
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
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 covers Foundation's units and measurements APIs.
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
let ingotMass = Measurement<UnitMass>(value: 400, unit: .ouncesTroy)
let formatter = MeasurementFormatter()
formatter.unitOptions = .providedUnit
formatter.string(from: ingotMass) // 400 oz t
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
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°
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 extends what we learned about units in the previous chapter to transform Xcode Playgrounds into an interactive physical calculator.
let takeoffWeight = (
emptyPlaneWeight +
payloadWeight +
fuelWeight +
pilotWeight
).converted(to: .pounds)
let canTakeOff = takeoffWeight < maximumTakeoffWeight // ???
MIT
Flight School is a book series for advanced Swift developers that explores essential topics in iOS and macOS development through concise, focused guides.
If you'd like to get in touch, feel free to message us on Twitter or email us at [email protected].