Swift Module 2 – Basics: values, types, operators and control flow
Integers, overflow traps and wrapping operators
Choose between Int, the sized integer types and Int128, predict what an overflow does in each kind of build, and wrap or detect overflow on purpose.
What you will learn
- Choose an integer type from Int, UInt, the sized types and Int128 by the range and size its values need
- Predict what an integer overflow does at compile time, in -Onone and -O builds and with -Ounchecked
- Use the wrapping operators and the reporting methods when a result may not fit
- Convert between integer types with init(exactly:), init(clamping:) and init(truncatingIfNeeded:)
- Predict integer division and the remainder operator for negative numbers
Before you start
What you’ll learn · Before you start
What you will learn
- Choose an integer type from Int, UInt, the sized types and Int128 by the range and size its values need
- Predict what an integer overflow does at compile time, in -Onone and -O builds and with -Ounchecked
- Use the wrapping operators and the reporting methods when a result may not fit
- Convert between integer types with init(exactly:), init(clamping:) and init(truncatingIfNeeded:)
- Predict integer division and the remainder operator for negative numbers
Before you start
On this page
An Int8 holds the whole numbers from −128 to 127, so 120 + 10 has nowhere to go. Swift does not hand back a wrong
number when that happens: a result that does not fit its type stops the program, where the arithmetic of C and
Objective-C would let the value overflow. Every integer type has such a limit, even Int, whose largest value on this
Mac is 9,223,372,036,854,775,807.
Here you will choose an integer type by the range and the memory its values need, predict what an overflow: A result too large or too small for the integer type that must hold it, such as Int8.max + 1. Swift stops the program instead of storing a wrong number, unless an & operator asks for wrapping. does at compile time and in each kind of build, wrap or detect overflow when you expect it, convert between integer types without surprises, and predict integer division and remainders for negative numbers.
Each integer type holds a fixed range
Every integer type has a smallest and a largest value, its min and max. This program prints them for most integer
types of the standard library, and the sizes of four of them in bytes:
// limits.swift: the smallest and largest value of each integer type on this Mac.
print("Int: ", Int.min, "to", Int.max, "(\(Int.bitWidth) bits)")
print("UInt: ", UInt.min, "to", UInt.max)
print("Int8: ", Int8.min, "to", Int8.max)
print("UInt8: ", UInt8.min, "to", UInt8.max)
print("Int16: ", Int16.min, "to", Int16.max)
print("Int32: ", Int32.min, "to", Int32.max)
print("Int64: ", Int64.min, "to", Int64.max)
print("Int128: ", Int128.min, "to", Int128.max)
print("UInt128:", UInt128.min, "to", UInt128.max)
print("Bytes per value: Int", MemoryLayout<Int>.size, "| Int8", MemoryLayout<Int8>.size,
"| Int32", MemoryLayout<Int32>.size, "| Int128", MemoryLayout<Int128>.size)
let seat: Int8 = 5
let count = 5
print("Int8 and Int compared:", seat == count, seat < count + 1) Output
Int: -9223372036854775808 to 9223372036854775807 (64 bits) UInt: 0 to 18446744073709551615 Int8: -128 to 127 UInt8: 0 to 255 Int16: -32768 to 32767 Int32: -2147483648 to 2147483647 Int64: -9223372036854775808 to 9223372036854775807 Int128: -170141183460469231731687303715884105728 to 170141183460469231731687303715884105727 UInt128: 0 to 340282366920938463463374607431768211455 Bytes per value: Int 8 | Int8 1 | Int32 4 | Int128 16 Int8 and Int compared: true true
Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O limits.swift -o limits && ./limits
Int has 64 bits here, the same range as Int64. Its size follows the platform: on a 32-bit platform Int is a
32-bit type that stops at 2,147,483,647, the Int32 line above. The sized types keep their size everywhere:
Int8, Int16, Int32 and Int64, and the unsigned UInt8 to UInt64, which start at 0 and go about twice as
high. Swift 6.0 added Int128 and UInt128 (SE-0425), for values past the 64-bit range, at 16 bytes each.
Version note
On Apple platforms, Int128 and UInt128 also need a recent system: Apple’s documentation lists macOS 15, iOS 18
and later, so the compiler refuses them in a program built for an older one.
The last line of the output shows a detail of mixed types: comparing an Int8 with an Int compiles and works, because the
standard library defines comparisons between any two integer types. Arithmetic does not mix them: seat + count
would be the error of the previous lesson, an operator that does not take an Int8 and an Int.
Which type to use depends on your values. Answer the questions in the table and it marks the types that fit, with the reason for each one that does not:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Type | Largest value | Mixing with other code | Same size everywhere | Memory per value |
|---|---|---|---|---|
| Int Size: the platform's word size: 64 bits on this Mac | Good 9223372036854775807 here: enough for counts and for amounts in paise | Good whole-number literals infer Int, so most code needs no conversion | Mixed 32 bits on a 32-bit platform, 64 bits on a 64-bit one | Mixed 8 bytes on this Mac |
| UInt Size: the platform's word size, with no negative values | Mixed up to 18446744073709551615, but 0 is its smallest value | Poor every mix with an Int needs a conversion | Mixed follows the platform, like Int | Mixed 8 bytes on this Mac |
| Int8 to Int32, UInt8 to UInt32 Size: 8, 16 or 32 bits on every platform | Poor Int8 stops at 127, UInt8 at 255, Int32 at 2147483647 | Poor a conversion at every mix with an Int | Good exactly 8, 16 or 32 bits wherever the code runs | Good 1 to 4 bytes: it adds up in arrays of millions |
| Int64, UInt64 Size: 64 bits on every platform | Good the same range as Int on this Mac | Mixed a type of its own: Int64(…) and Int(…) where it meets Int | Good 64 bits wherever the code runs | Mixed 8 bytes |
| Int128, UInt128 Size: 128 bits, from Swift 6.0 | Good up to 170141183460469231731687303715884105727 | Poor a conversion wherever it meets Int | Good 128 bits wherever the code runs | Poor 16 bytes per value |
How the answers decide
- “How large can your values get?” Larger than that: rules out Int, UInt, Int8 to Int32, UInt8 to UInt32 and Int64, UInt64. Only the 128-bit types go past 9223372036854775807; with Int or Int64 the first result that large stops the program.
- “How large can your values get?” Within ±9223372036854775807: adds a caveat to Int128, UInt128. Int already holds values this size; Int128 doubles the memory per value and needs a conversion wherever it meets Int.
- “Can a value ever be negative?” Yes: rules out UInt. UInt has no negative values: 0 - 1 stops the program, and 0 &- 1 wraps round to the largest value.
- “Can a value ever be negative?” No, never: adds a caveat to UInt. Swift's own guide prefers Int even for values that are never negative: it saves conversions and matches inferred types.
- “Must the values keep a fixed size, for a file format, a network message or a C library?” Yes: adds a caveat to Int and UInt. Int and UInt take the platform's size, 32 bits on a 32-bit platform, so a format with a fixed size needs a sized type.
- “Must the values keep a fixed size, for a file format, a network message or a C library?” No and “Will you keep millions of them in memory at once?” No: adds a caveat to Int8 to Int32, UInt8 to UInt32 and Int64, UInt64. With no fixed format and no memory to save, a sized type only adds conversions where it meets Int.
- “Will you keep millions of them in memory at once?” Yes: adds a caveat to Int, Int64, UInt64 and Int128, UInt128. At 8 or 16 bytes a value, a million values take 8 or 16 MB; a smaller type that holds the range saves most of it.
For most values the answer is Int. The Swift Programming Language recommends it even for values that are never
negative, because whole-number literals infer Int and code that uses one type needs no conversions. A sized type
earns its place when a file format, a network message or a C library fixes the size, or when millions of values must
fit in memory.
Checkpoint
Check: Integer types and their ranges
An overflow stops the program instead of giving a wrong number
When the compiler can see every value of a calculation, it does the arithmetic itself and refuses a result that does not fit:
// literal_overflow.swift: values the compiler can see do not fit their type.
let seatsInCoach: Int8 = 200
let lastSeat: Int8 = Int8.max + 1
print(seatsInCoach, lastSeat) Output (exit status 1)
Printed as an error (standard error)
literal_overflow.swift:3:31: error: arithmetic operation '127 + 1' (on type 'Int8') results in an overflow 1 | // literal_overflow.swift: values the compiler can see do not fit their type. 2 | let seatsInCoach: Int8 = 200 3 | let lastSeat: Int8 = Int8.max + 1 | `- error: arithmetic operation '127 + 1' (on type 'Int8') results in an overflow 4 | print(seatsInCoach, lastSeat) 5 | literal_overflow.swift:2:26: error: integer literal '200' overflows when stored into 'Int8' 1 | // literal_overflow.swift: values the compiler can see do not fit their type. 2 | let seatsInCoach: Int8 = 200 | `- error: integer literal '200' overflows when stored into 'Int8' 3 | let lastSeat: Int8 = Int8.max + 1 4 | print(seatsInCoach, lastSeat)
Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O literal_overflow.swift -o literal_overflow && ./literal_overflow
Both lines are refused before anything runs: the literal 200 is larger than any Int8, and so is Int8.max + 1.
Usually the values exist only while the program runs, and then the check happens when the line runs. This program adds
the number on its command line to Int.max, and the session builds it three times, once for each
optimisation level: The compiler option that sets how much swiftc optimises: -Onone (none, the debug default), -O, -Osize and -Ounchecked, which also removes runtime safety checks such as the overflow check. it tries, after asking swiftc what the levels do:
// one_more.swift: adds the number given on the command line to Int.max.
let largest = Int.max
print("largest:", largest)
let words = CommandLine.arguments // ["./one_more", "1"] for ./one_more 1
if words.count == 2, let extra = Int(words[1]) {
let next = largest + extra
print("next:", next)
} from session import run, version
# What the optimisation levels say they do, then one_more.swift built three ways and run with the number 1.
version()
run("""
swiftc --help | grep -E '^ -O'
swiftc -Onone one_more.swift -o one_more
!./one_more 1
swiftc -O one_more.swift -o one_more
!./one_more 1
swiftc -Ounchecked one_more.swift -o one_more
./one_more 1
""") Output
# Apple Swift version 6.4 (swiftlang-6.4.0.34.1) $ swiftc --help | grep -E '^ -O' -Onone Compile without any optimization -Osize Compile with optimizations and target small code size -Ounchecked Compile with optimizations and remove runtime safety checks -O Compile with optimizations $ swiftc -Onone one_more.swift -o one_more $ ./one_more 1 largest: 9223372036854775807 [exit status 133] $ swiftc -O one_more.swift -o one_more $ ./one_more 1 largest: 9223372036854775807 [exit status 133] $ swiftc -Ounchecked one_more.swift -o one_more $ ./one_more 1 largest: 9223372036854775807 next: -9223372036854775808
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 overflow_session.py
In the -Onone and -O builds, the program printed largest and then stopped at largest + extra: no next: line, and
an exit status of 133, which a shell reports for a program stopped by signal 5, a
trap: An immediate stop of a program at a failed runtime check, such as an integer overflow or a division by zero. No line after it runs; a shell reports the stop as an exit status of 128 plus the signal's number. (133 is 128 + 5). Nothing was printed about why, so the last line of output is your only
clue to where it stopped. -Ounchecked is the one level whose description says it removes runtime safety checks, and
its build printed next: -9223372036854775808, the most negative Int, with no error at all.
-Ounchecked for speed
-Ounchecked makes a program faster only by removing the checks that stop it on a wrong result, so an overflow then
prints a wrong number, as next: did above. Keep -O for release builds, and use the & operators of the next
section where wrapping is really wanted.
Follow the four possible outcomes through the compiler’s and the running program’s decisions:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- A value the compiler sees. In literal_overflow.swift, Int8.max + 1 adds two values known while compiling. The compiler works the sum out, finds 128, which no Int8 holds, and refuses to build the program.
- A value that exists at run time. In one_more.swift, the number to add comes from the command line, so the compiler cannot know the sum. It builds the program and leaves the check of largest + extra to the moment the line runs.
- A plain +. The + in largest + extra asks for the exact sum, not a wrapped one, so the result must fit or the program cannot go on.
- A checked build. Built with -Onone or -O, the program keeps its overflow checks. Of the four optimisation levels in the help text of swiftc, only -Ounchecked removes runtime safety checks.
- The trap. Run with the number 1, the check fails and the program stops at once. largest was printed, next never was, and the exit status was 133 in both builds.
- The same sum with &+. With &+ instead of + the program asks for wrapping. Int.max &+ 1 keeps the 64 bits that fit, which are Int.min, and the program goes on without any trap, as wrapping.swift shows.
- An unchecked build. Built with -Ounchecked, the plain + is no longer checked. one_more printed next as -9223372036854775808, a wrong number with no warning at all.
- Four outcomes. A compile error, a trap, a wrapped value you asked for, or a wrong number you did not. Only the last one is silent, which is why -Ounchecked is rarely the right choice.
The two builds a project usually makes keep the check. The Swift Package Manager builds its debug configuration,
swift build, with -Onone, and its release configuration, swift build -c release, with -O.
Key idea
An integer result that does not fit stops the program: at compile time when the compiler can see the values, and at
run time with a trap in both debug and release builds. Only -Ounchecked removes the check, and then the result is
silently wrong.
Checkpoint
Check: Overflow traps and optimisation levels
Wrapping operators keep the bits that fit
Some calculations should start again at the other end of the range: a sequence number that counts 254, 255, 0, or the
arithmetic of checksums and hash functions, which work on a fixed number of bits. The
overflow operators: &+, &- and &*: integer arithmetic that keeps only the bits that fit the type, so a result past the maximum wraps round to the minimum instead of stopping the program. &+, &- and &* do that: they keep the bits that fit the
type and drop the rest. The ReportingOverflow methods do the arithmetic the same way and also say whether anything
was dropped:
// wrapping.swift: overflow on purpose with &+, &- and &*, and overflow reported.
let speed: Int8 = 120
print("120 &+ 10 in Int8:", speed &+ 10)
print("120 &* 2 in Int8: ", speed &* 2)
print("Int8.min &- 1: ", Int8.min &- 1)
print("UInt8.max &+ 1: ", UInt8.max &+ 1)
print("UInt8.min &- 1: ", UInt8.min &- 1)
print("120 &+ 10 in UInt8:", UInt8(120) &+ 10)
print("Int.max &+ 1 is Int.min:", Int.max &+ 1 == Int.min)
var packet: UInt8 = 254 // an 8-bit sequence number
packet = packet &+ 1
print("packet:", packet)
packet = packet &+ 1
print("packet:", packet)
let sum = speed.addingReportingOverflow(10)
print("reported:", sum.partialValue, sum.overflow)
let safe = speed.addingReportingOverflow(7)
print("reported:", safe.partialValue, safe.overflow) Output
120 &+ 10 in Int8: -126 120 &* 2 in Int8: -16 Int8.min &- 1: 127 UInt8.max &+ 1: 0 UInt8.min &- 1: 255 120 &+ 10 in UInt8: 130 Int.max &+ 1 is Int.min: true packet: 255 packet: 0 reported: -126 true reported: 127 false
Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O wrapping.swift -o wrapping && ./wrapping
120 &+ 10 is −126 in an Int8, but the same bits are 130 in a UInt8, and the packet number went from 255 to 0.
The bits explain both results. Step through them:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- 120 in bits. An Int8 has eight bits. 120 is 64 + 32 + 16 + 8, so its bits are 0111 1000; the leftmost bit, the top bit, is 0.
- 10 in bits. 10 is 8 + 2, so its bits are 0000 1010.
- The sum. Adding them bit by bit, with carries, gives 1000 0010. The top bit is now 1, because 130 needs it.
- Too large for Int8. 130 is larger than Int8.max, 127. A plain + would stop the program here; &+ keeps the eight bits instead.
- Read as an Int8. In a signed type the top bit counts as −128, not +128, which is called two's complement. 1000 0010 is −128 + 2, so 120 &+ 10 is −126, as wrapping.swift printed.
- Read as a UInt8. In an unsigned type the top bit counts as +128. The same eight bits are 130, which fits a UInt8, so UInt8(120) &+ 10 is plainly 130.
- Past the last bit. 255 is 1111 1111, every bit set. Adding 1 gives 1 0000 0000, nine bits; &+ drops the ninth, leaving 0, which is how the packet number went from 255 to 0.
- What wrapping is. The & operators keep the bits that fit and drop the rest. That is the whole rule, for every integer type and every size.
A signed type stores negative numbers in two's complement: The way signed integers are stored: the top bit counts as minus 2 to the power of (bits − 1), so in an Int8 the bits 1000 0010 mean −128 + 2 = −126., where the top bit counts
as −128 in an Int8 (and as minus 2 to the power 63 in an Int), so wrapping past the maximum lands on the minimum.
addingReportingOverflow(10) returned (partialValue: -126, overflow: true): the wrapped value, and a Bool that says
the exact sum did not fit. subtractingReportingOverflow, multipliedReportingOverflow(by:) and
dividedReportingOverflow(by:) do the same for the other operations, so code can catch an overflow and decide what
to do instead of stopping.
Checkpoint
Check: Wrapping and reporting operators
Converting between integer types can stop, clamp or cut
A reading of 300 from a sensor, kept in an Int, has to go into a UInt8, which holds 0 to 255. Each initializer
of the standard library does something different with it:
// narrowing.swift: four ways to put the Int 300 into a UInt8, which holds 0 to 255.
let reading = 300
if let exact = UInt8(exactly: reading) {
print("exactly:", exact)
} else {
print("exactly: no UInt8 holds", reading)
}
print("clamping:", UInt8(clamping: reading))
print("truncatingIfNeeded:", UInt8(truncatingIfNeeded: reading))
let small = 200
print("plain UInt8(small):", UInt8(small))
print("Int(\"42\"):", Int("42") == 42)
print("Int(\"4 2\") is nil:", Int("4 2") == nil)
print("Int(\"1_000\") is nil:", Int("1_000") == nil)
print("Int(\"99999999999999999999\") is nil:", Int("99999999999999999999") == nil)
print("Int(\"2A\", radix: 16):", Int("2A", radix: 16) == 42)
print("1_00_000 == 100_000:", 1_00_000 == 100_000) Output
exactly: no UInt8 holds 300
clamping: 255
truncatingIfNeeded: 44
plain UInt8(small): 200
Int("42"): true
Int("4 2") is nil: true
Int("1_000") is nil: true
Int("99999999999999999999") is nil: true
Int("2A", radix: 16): true
1_00_000 == 100_000: true
Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O narrowing.swift -o narrowing && ./narrowing
UInt8(exactly:) gives nil when the value does not fit, so if let can choose what to do. UInt8(clamping:)
gives the nearest value that fits, 255. UInt8(truncatingIfNeeded:) keeps the lowest 8 bits: 300 is 1 0010 1100 in
binary, and its last eight bits, 0010 1100, are 44, the same rule as the & operators. The plain UInt8(small) works
when the value fits. When it does not, it traps like an overflow:
// narrow_trap.swift: a plain conversion that cannot fit stops the program.
let reading = 300
print("reading:", reading)
let byte = UInt8(reading)
print("byte:", byte) from session import run, version
# narrow_trap.swift in a debug build and in a release build.
version()
run("""
swiftc -Onone narrow_trap.swift -o narrow_trap
!./narrow_trap
swiftc -O narrow_trap.swift -o narrow_trap
!./narrow_trap
""") Output
# Apple Swift version 6.4 (swiftlang-6.4.0.34.1) $ swiftc -Onone narrow_trap.swift -o narrow_trap $ ./narrow_trap reading: 300 Swift/arm64e-apple-macos.swiftinterface:13743: Fatal error: Not enough bits to represent the passed value [exit status 133] $ swiftc -O narrow_trap.swift -o narrow_trap $ ./narrow_trap reading: 300 [exit status 133]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 narrow_session.py
The debug build printed a reason, Fatal error: Not enough bits to represent the passed value, before it stopped; the
release build stopped with the same status and no message. Turning text into a number is safer: Int("…") never
traps. It gives nil for anything but an optional sign and digits, including the underscores that literals allow, and
for a number too large for Int, as the second half of narrowing.swift shows.
Indian digit grouping
Underscores may go anywhere between the digits of a literal, so a lakh written the Indian way, 1_00_000, is the
same 100000 as 100_000. Printing numbers with lakh and crore separators is a formatting job, which the module on
Foundation covers.
Checkpoint
Check: Conversions between integer types
Integer division cuts towards zero
Dividing one Int by another gives an Int, so the fraction has to go, and it goes the same way as in a conversion:
towards zero. The remainder operator % gives what is left over, with the sign of the number on its left:
// division.swift: integer division cuts towards zero; % keeps the sign of the left side.
print("7 / 2 =", 7 / 2, " -7 / 2 =", -7 / 2, " 7 / -2 =", 7 / -2)
print("7 % 2 =", 7 % 2, " -7 % 2 =", -7 % 2, " 7 % -2 =", 7 % -2)
let billPaise = 1_000
let people = 3
let share = billPaise / people
let left = billPaise % people
print("Each pays", share, "paise; left over:", left)
print("Check:", share * people + left, "paise")
let daysBack = -1 // one day before a Monday, day 0
print("-1 % 7 =", daysBack % 7, " wrapped into 0...6:", (daysBack % 7 + 7) % 7) Output
7 / 2 = 3 -7 / 2 = -3 7 / -2 = -3 7 % 2 = 1 -7 % 2 = -1 7 % -2 = 1 Each pays 333 paise; left over: 1 Check: 1000 paise -1 % 7 = -1 wrapped into 0...6: 6
Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O division.swift -o division && ./division
-7 / 2 is −3, not −4, and -7 % 2 is −1. The rule that ties them together is a == (a / b) * b + a % b, which the
bill split checks: three people pay 333 paise each, and the 1 paisa left over still has to be paid by someone.
The Swift Programming Language points out that % is a remainder, not a mathematical modulo, because of this sign: one
day before day 0 of a week is −1 % 7 = −1, not 6. Adding 7 and taking the remainder again, (x % 7 + 7) % 7, gives
the 6 that a weekday calculation needs.
Using % as modulo for negative numbers
(today - 1) % 7 gives −1 when today is 0, and a weekday table has no entry −1. Write ((today - 1) % 7 + 7) % 7,
which gives 6, or keep the value from going negative in the first place.
Division has one more way to fail: a divisor of zero. When the zero arrives while the program runs, the division traps, like an overflow. This program takes the number of people from its command line and is run with none:
// split_by_zero.swift: a bill split between the people named after the program: none here.
let people = CommandLine.arguments.count - 1
print("people:", people)
print("each pays:", 1_000 / people) from session import run, version
# split_by_zero.swift run without arguments, in a debug build and in a release build.
version()
run("""
swiftc -Onone split_by_zero.swift -o split_by_zero
!./split_by_zero
swiftc -O split_by_zero.swift -o split_by_zero
!./split_by_zero
""") Output
# Apple Swift version 6.4 (swiftlang-6.4.0.34.1) $ swiftc -Onone split_by_zero.swift -o split_by_zero $ ./split_by_zero people: 0 Swift/arm64e-apple-macos.swiftinterface:45375: Fatal error: Division by zero [exit status 133] $ swiftc -O split_by_zero.swift -o split_by_zero $ ./split_by_zero people: 0 [exit status 133]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 zero_session.py
Both builds stopped after people: 0, with the status of a trap. As with the conversion, only the debug build said
why, Fatal error: Division by zero, so check a divisor that comes from outside the program before you divide by it.
Checkpoint
Check: Integer division and remainders
Practise: an order total that never traps
A wholesale order costs its quantity times the unit price, in paise, plus a delivery fee. A quantity mistyped as
4,000,000,000,000 for an item at 9,999,999 paise must not stop the program that computes it. Write
orderTotal(quantity:unitPaise:deliveryPaise:), which returns the total, or nil when any step overflows. Its result
type, Int?, means an Int or nil, the kind of result Int(line) gave in the first program lesson; the lesson on
optionals covers it in full.
Try it on an order of 1,200 items at 4,999 paise with a delivery fee of 15,000 paise, on the mistyped order, and on
one item that costs Int.max paise. Then compare your program with this one:
// order_total.swift: an order total that reports overflow instead of stopping the program.
func orderTotal(quantity: Int, unitPaise: Int, deliveryPaise: Int) -> Int? {
let items = quantity.multipliedReportingOverflow(by: unitPaise)
if items.overflow {
return nil
}
let total = items.partialValue.addingReportingOverflow(deliveryPaise)
if total.overflow {
return nil
}
return total.partialValue
}
if let total = orderTotal(quantity: 1_200, unitPaise: 4_999, deliveryPaise: 15_000) {
print("Order 1:", total, "paise")
} else {
print("Order 1: too large to compute")
}
if let total = orderTotal(quantity: 4_000_000_000_000, unitPaise: 9_999_999, deliveryPaise: 15_000) {
print("Order 2:", total, "paise")
} else {
print("Order 2: too large to compute")
}
if let total = orderTotal(quantity: 1, unitPaise: Int.max, deliveryPaise: 1) {
print("Order 3:", total, "paise")
} else {
print("Order 3: too large to compute")
} Output
Order 1: 6013800 paise Order 2: too large to compute Order 3: too large to compute
Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O order_total.swift -o order_total && ./order_total
multipliedReportingOverflow(by:) checks the product and addingReportingOverflow(_:) the sum, and either overflow
returns nil before a wrong total can be used. The first order costs 6,013,800 paise; the other two report that they
are too large, where a plain * and + would have stopped the program. Then test what you know of the whole lesson.
The questions begin with the idea you have shown least so far, and each right answer brings a harder one:
Practice that adapts to you
Practise this lesson
14 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up: what happens when an integer calculation overflows in Swift?
The program stops, unless you asked for something else. When the compiler can see the values, as in
let x: Int8 = Int8.max + 1, the code does not compile. When the values exist only at run time, the operation traps:
the program ends at once, in debug (-Onone) and release (-O) builds alike. Swift’s documentation gives the reason:
arithmetic that refuses to overflow avoids unexpected results from values that have left their range, which C and
Objective-C allow. If wrapping is what you want, as for hashes or sequence numbers, the
overflow operators &+, &- and &* keep the low bits, and methods such as addingReportingOverflow(_:) return the
wrapped value with a flag, so the code can handle the overflow. Only -Ounchecked removes the check, and then results
are silently wrong.
Key takeaways
- Every integer type has a fixed range:
Intis 64 bits on this Mac and follows the platform, the sized types fromInt8toInt64keep their size everywhere, andInt128andUInt128arrived in Swift 6.0; useIntunless a format or memory fixes the size. - A result that does not fit is a compile error when the compiler can see the values, and a trap that stops the
program at run time, with exit status 133 here, in
-Ononeand-Obuilds alike; only-Ouncheckedremoves the check and lets wrong numbers through. &+,&-and&*keep the bits that fit, so values wrap from the maximum to the minimum in two’s complement, and theReportingOverflowmethods return the wrapped value with a flag that says whether it overflowed.init(exactly:)givesnil,init(clamping:)the nearest value andinit(truncatingIfNeeded:)the low bits, while a plain conversion that does not fit traps;Int("…")givesnilinstead of trapping.- Integer division cuts the fraction towards zero,
%keeps the sign of the left-hand number, and a zero divisor at run time traps.
Terms in this lesson
- optimisation level
- The compiler option that sets how much swiftc optimises: -Onone (none, the debug default), -O, -Osize and -Ounchecked, which also removes runtime safety checks such as the overflow check.
- overflow
- A result too large or too small for the integer type that must hold it, such as Int8.max + 1. Swift stops the program instead of storing a wrong number, unless an & operator asks for wrapping.
- overflow operators
- &+, &- and &*: integer arithmetic that keeps only the bits that fit the type, so a result past the maximum wraps round to the minimum instead of stopping the program.
- trap
- An immediate stop of a program at a failed runtime check, such as an integer overflow or a division by zero. No line after it runs; a shell reports the stop as an exit status of 128 plus the signal's number.
- two's complement
- The way signed integers are stored: the top bit counts as minus 2 to the power of (bits − 1), so in an Int8 the bits 1000 0010 mean −128 + 2 = −126.
References
- The Swift Programming Language (6.4): The Basics, Integers (Swift.org)
- The Swift Programming Language (6.4): Basic Operators (Swift.org)
- The Swift Programming Language (6.4): Advanced Operators, Overflow Operators (Swift.org)
- FixedWidthInteger addingReportingOverflow(_:) (Swift.org)
- BinaryInteger init(clamping:) (Swift.org)
- BinaryInteger init(truncatingIfNeeded:) (Swift.org)
- Int128 (Swift.org)
- Int128: availability on Apple platforms (Apple)
- SE-0425: 128-bit Integer Types (Swift Evolution (The Swift Project))
- Swift Package Manager: Using build configurations (Swift.org)
- Binary Integer Operators (Swift.org)
- BinaryInteger %(_:_:): the remainder operator (Swift.org)
- signal(3): the signals of macOS, signal 5 SIGTRAP (Apple)
- Bash Reference Manual: Exit Status (GNU Project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress