Zero to iOS Hero 5: Operators, conversion, and overflow
This is part 5 of the Zero to iOS Hero series.
An expression combines values to produce another value. Swift operators make those combinations compact, but the type system still decides which combinations are legal and what the result means.
That is why 3 + 4, 3 / 4, 3.0 / 4.0, and UInt8.max &+ 1 tell four different stories.
Read an expression as a typed tree
Consider a relevance score:
let rawScore = Double(tagPoints) + freshness * maximumFreshnessBonusThe expression is evaluated from smaller parts:
tagPoints --explicit conversion---> Double |freshness * maximumFreshnessBonus --->+---> rawScore: DoubleEvery operand has a type. Every operator has valid operand combinations. The whole expression has a resulting type.
When a compiler diagnostic points to a large expression, inspect its smaller branches before rewriting the entire line.
Arithmetic preserves numeric meaning
Swift supplies the common arithmetic operators:
let sum = 8 + 3let difference = 8 - 3let product = 8 * 3let integerQuotient = 8 / 3let floatingQuotient = 8.0 / 3.0let remainder = 8 % 3The values are:
| Expression | Result | Result type |
|---|---|---|
8 + 3 | 11 | Int |
8 - 3 | 5 | Int |
8 * 3 | 24 | Int |
8 / 3 | 2 | Int |
8.0 / 3.0 | Approximately 2.6667 | Double |
8 % 3 | 2 | Int |
Integer division discards the fractional remainder. It does not round to the nearest integer.
The % operator is a remainder operation. With negative operands, do not assume its result follows every mathematical modulo convention. Test the exact rule your domain expects.
Assignment changes a binding
The assignment operator stores a new value:
var score = 10score = 12Compound assignment combines an operation with reassignment:
score += 3score *= 2After those lines, score is 30.
Swift’s assignment expression does not return a value. Code such as if x = y cannot silently turn an assignment into a Boolean condition. This removes a common typo from C-shaped languages.
Comparisons produce Boolean values
Comparison operators answer questions:
let rating = 4
let isPerfect = rating == 5let needsReview = rating != 5let isRecommended = rating >= 4let isValid = rating > 0 && rating <= 5The results are Bool values. They can be stored, printed, passed to a function, or used by control flow.
Use parentheses when they make the business rule easier to scan:
let shouldFeature = isFavorite && (rating >= 4)The compiler knows the precedence without the parentheses. A teammate should not need to recite the precedence table to understand the rule.
Boolean operators short-circuit
The logical operators are negation (!), conjunction (&&), and disjunction (||).
&& stops when its left side is false. || stops when its left side is true.
let hasSearchText = !query.isEmptylet shouldSearch = hasSearchText && noteCount > 0Short-circuiting is part of the expression’s behavior. It can prevent unnecessary work and can protect a later expression that is only safe after an earlier condition passes.
Keep side effects out of complex Boolean expressions. A condition should read as a decision, not as a hidden workflow.
Numeric types do not silently coerce
Existing values of different numeric types need an explicit conversion:
let tagPoints = 36let freshnessBonus = 15.0
let total = Double(tagPoints) + freshnessBonusThis fails:
let total = tagPoints + freshnessBonusSwift does not guess whether the intended result should be an Int, a Double, or something else. Writing Double(tagPoints) makes the loss model visible.
The direction matters:
let precise = 4.9let whole = Int(precise) // 4Converting a finite Double to Int truncates the fractional part toward zero. It does not round. If the product rule says “nearest whole point,” round first:
let whole = Int(precise.rounded()) // 5Conversion is a design decision, not a ceremony to silence the compiler.
Run the bounded relevance score
The Field Notes checkpoint combines integer tag points with a floating-point freshness bonus:
Execution sends this source to the project runner. It uses Swift 6.3.3 on Linux for standard-library code, not the Apple SDK, an iOS simulator, or a device.
Compiler diagnostics
(none)
Standard output
(no stdout)
Standard error
(no stderr)
Expected output:
Relevance: 51/100UInt8.max &+ 1: 0The important sequence is explicit:
Int tag points | vconvert to Double | vadd floating-point bonus | vround to a whole score | vclamp to 0...100 | vconvert to UInt8The conversion to UInt8 happens only after the program proves the value is inside the target range. Reversing those steps would ask the narrow type to accept an unchecked value.
The editor uses Swift 6.3.3 on Linux when available. It proves this standard-library calculation, not an Apple SDK ranking interface.
Clamping protects a domain range
The score is bounded with nested standard-library functions:
let boundedScore = min(max(roundedScore, 0), 100)Read from the inside out:
max(roundedScore, 0)prevents a negative result.min(..., 100)prevents a result above the maximum.
Clamping is correct when out-of-range inputs should map to the nearest boundary. It is wrong when an out-of-range input indicates corrupt data that should be rejected. The operator is easy; the domain policy requires judgment.
Default integer overflow is checked
Each fixed-width integer type has a finite range:
let smallest = UInt8.min // 0let largest = UInt8.max // 255Ordinary arithmetic does not silently wrap across that boundary:
var count = UInt8.maxcount += 1 // Runtime trapThe trap prevents 255 + 1 from quietly becoming 0 in ordinary arithmetic. A crash is still undesirable in a shipped product, so validate external values and use an overflow-reporting operation when exceeding the range is an expected possibility.
Overflow-reporting operations keep control
Fixed-width integers can report overflow without trapping:
let result = UInt8.max.addingReportingOverflow(1)
print(result.partialValue) // 0print(result.overflow) // trueThe returned tuple gives the wrapped partial value and an overflow flag. Use the partial value only under a policy that defines what it means.
A score pipeline might reject an overflow:
let result = current.addingReportingOverflow(increment)precondition(!result.overflow, "Score arithmetic exceeded UInt8")let updated = result.partialValueIn production, a throwing function or validation result may be more appropriate than a precondition. Error modeling arrives later in the foundations arc.
Wrapping operators opt in
Swift’s overflow operators are &+, &-, and &*:
let wrapped = UInt8.max &+ 1 // 0The ampersand is an explicit opt-in to fixed-width wrapping. It is useful for algorithms and protocols whose rules are defined in terms of bits. It is usually wrong for money, counts, ratings, and user-visible scores.
The runnable example prints one wrapping result so the behavior is visible. Its relevance calculation uses normal checked arithmetic and a deliberate clamp.
Operator precedence is not a product rule
Multiplication binds more tightly than addition:
let score = base + freshness * weightThat expression is equivalent to:
let score = base + (freshness * weight)Precedence explains evaluation order. It does not explain why freshness is multiplied by that weight. Name intermediate values when the domain meaning matters:
let freshnessBonus = freshness * maximumFreshnessBonuslet rawScore = Double(tagPoints) + freshnessBonusThe second version gives the calculation vocabulary.
Common wrong moves
- Mixing
IntandDoubleuntil the compiler accepts something: Choose the intended numeric domain, then convert at a named boundary. - Using
Int(value)as rounding: It truncates. Apply the required rounding rule first. - Choosing
UIntbecause a value should not be negative: Validate the domain. Unsigned arithmetic can still underflow and complicate differences. - Using wrapping operators to prevent a crash: Wrapping changes the result. It is not generic error recovery.
- Compressing a business formula into one line: Name intermediate values that explain the policy.
- Assuming floating-point values represent decimal fractions exactly: Treat comparisons and rounding according to the domain’s tolerance and precision needs.
Practice
Change one part of the runnable program at a time:
- Set
freshnessto1.0and predict the score. - Set
matchingTagCountto20and confirm the score clamps to100. - Remove
Double(tagPoints)and read the type error. - Change
.rounded()to.rounded(.down)and compare a score with a fractional part. - Replace the wrapping expression with
UInt8.max.addingReportingOverflow(1)and print both tuple elements. - Try ordinary
UInt8.max + 1as a separate experiment and classify whether the compiler or runtime catches it.
Keep the overflow experiment separate from useful work. A deliberate failure should be easy to remove and easy to explain.
Checkpoint
You should now be able to explain:
- How operand types determine an expression’s valid operations and result.
- Why integer and floating-point division produce different answers.
- Why numeric conversion is explicit.
- Why conversion and rounding are separate decisions.
- When clamping is a product policy rather than a universal repair.
- How checked, reporting, and wrapping arithmetic differ.
The next post feeds these expressions into conditions, switches, loops, ranges, and patterns.
Series navigation
- Previous: Part 4: Values, variables, types, and inference
- Next: Part 6: Control flow, ranges, and patterns
- Series index: Zero to iOS Hero
References
- Operator behavior: The Swift Programming Language chapter Basic Operators defines assignment, arithmetic, comparison, Boolean, range, and precedence behavior.
- Numeric types and conversion: The Basics covers integer ranges, floating-point types, explicit numeric conversion, and type safety.
- Overflow choices: Advanced Operators documents checked integer arithmetic and the explicit overflow operators.
Related topics
- Coding concepts, reasoning about how repeated operations scale.
- Binary search, a catalog where midpoint arithmetic and numeric boundaries matter.
- Testing, boundary checks that make numeric policies executable.