Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Beginner
  • 15 minutes
  • Examples run with Swift 6.4 and Python 3.14.8
  • By MySmartCoPilot
Free: 15 minutes

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 ranges of nine integer types Swift · limits.swift
// 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:

Interactive · answer and compare Which integer type fits your values? Answer four questions about the values you need to store. The table marks the types that fit and says why the others do not.
How large can your values get?
Can a value ever be negative?
Must the values keep a fixed size, for a file format, a network message or a C library?
Will you keep millions of them in memory at once?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Five integer types against the four things that decide between them (ranges and sizes from limits.swift)
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

  1. One question A program counts the passengers of a metro line in a day: never negative, and never more than a few million. Which type fits best?

    Choose one answer.

    Show the answer: One question

    Answer: Int

    Use Int unless something fixes the size or the range is too small. A daily passenger count is far inside Int's range, and being non-negative is not a reason for UInt.

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: two values that cannot be Int8 Swift · literal_overflow.swift
// 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: Int.max plus a number from the command line Swift · one_more.swift
// 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)
}
The same program at -Onone, -O and -Ounchecked Python · overflow_session.py
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:

Interactive · stage by stage When a result does not fit its type Step through four results that do not fit, from the compiler's check to what the running program does.
When a result does not fit its type, step 8 of 8. Showing Known when compiling? (no: checked when it runs), An &+, &- or &*? (no: a plain +), Built with -Ounchecked? (yes: a wrong number), The program stops (never happens).Known when compiling?no: checked when it runsAn &+, &- or &*?no: a plain +Built with -Ounchecked?yes: a wrong numberThe program stopsnever happens

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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

  1. Question 1 of 2 one_more.swift is part of a package built with swift build -c release. What happens when it is run with the number 1?

    Choose one answer.

    A hint

    Which optimisation level does a release build use, and does it keep the checks?

    Show the answer: Question 1 of 2

    Answer: It prints largest, then stops with a trap

    swift build -c release compiles with -O, and -O keeps every overflow check. The program prints largest and traps at largest + extra, exactly as the -O build in the session did.

  2. Question 2 of 2 Which of these is refused by the compiler instead of failing while the program runs?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: let seats: Int8 = 100 + 28

    When every value is a literal or a constant the compiler can see, it computes the result itself, and 100 + 28 = 128 does not fit an Int8. Values that arrive at run time are checked when the line runs.

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: wrapping, and overflow reported Swift · wrapping.swift
// 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:

Interactive · step by step 120 &+ 10 in eight bits Step through the addition bit by bit, then read the same eight bits as an Int8 and as a UInt8.
120 &+ 10 in eight bits, step 8 of 8. Showing 0111 1000, 0000 1010, 1000 0010, 130 does not fit, −128 + 2 = −126, 128 + 2 = 130, 1111 1111 + 1, 0000 0000.0111 1000120 = 64 + 32 + 16 + 80000 101010 = 8 + 21000 0010the eight bits of the sum130 does not fitInt8 stops at 127−128 + 2 = −126read as an Int8128 + 2 = 130read as a UInt81111 1111 + 1UInt8.max &+ 10000 0000the ninth bit is dropped

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. 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.
  2. 10 in bits. 10 is 8 + 2, so its bits are 0000 1010.
  3. The sum. Adding them bit by bit, with carries, gives 1000 0010. The top bit is now 1, because 130 needs it.
  4. Too large for Int8. 130 is larger than Int8.max, 127. A plain + would stop the program here; &+ keeps the eight bits instead.
  5. 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.
  6. 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.
  7. 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.
  8. 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.

Programmer Calculator (Bitwise) Add and multiply at 8 to 128 bits, signed or unsigned, and watch the bits that overflow.

Checkpoint

Check: Wrapping and reporting operators

  1. Question 1 of 2 What is Int8(100) &+ 100?

    Type a number.

    A hint

    The exact sum is 200. Which value does an Int8 reach by going 256 back?

    Show the answer: Question 1 of 2

    Answer: -56

    The exact sum, 200, is 73 past Int8.max. Wrapping keeps the low eight bits, which is the same as subtracting 256: 200 − 256 = −56.

  2. Question 2 of 2 What does UInt8(250).addingReportingOverflow(10) return?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: (partialValue: 4, overflow: true)

    addingReportingOverflow(_:) returns the wrapped value with a flag. 250 + 10 = 260, which wraps to 4, and overflow is true, so the code can decide what to do.

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 make the Int 300 a UInt8 Swift · narrowing.swift
// 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: UInt8(300) Swift · narrow_trap.swift
// 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)
A conversion that does not fit, in a debug and a release build Python · narrow_session.py
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

  1. One question let t = -20 holds a temperature, an Int. Which conversion to UInt8 gives 0?

    Choose one answer.

    Show the answer: One question

    Answer: UInt8(clamping: t)

    Each conversion answers a different question. Clamping gives the nearest value, 0; truncating keeps bits, 236; exactly gives nil; the plain conversion stops the program.

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: quotients, remainders and a bill split three ways Swift · division.swift
// 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 split between nobody Swift · split_by_zero.swift
// 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)
Dividing by zero at run time Python · zero_session.py
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

  1. Question 1 of 2 What is -17 % 5?

    Type a number.

    A hint

    The remainder takes the sign of the number on its left.

    Show the answer: Question 1 of 2

    Answer: -2

    −17 / 5 is −3, because division cuts towards zero, and −17 = 5 × (−3) + (−2), so the remainder is −2. It keeps the sign of −17.

  2. Question 2 of 2 1,000 paise are split between 6 people with / and %. How many paise are left over?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 4 paise

    1000 / 6 is 166, so six shares make 996 paise, and 1000 % 6 is the 4 paise left over: 166 × 6 + 4 = 1000.

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: one way to write it Swift · order_total.swift
// 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.

  1. Question 1 of 14 How many bits does Int have on a 64-bit platform such as this Mac?

    Type a number.

    Show the answer: Question 1 of 14

    Answer: 64 bits

    Int has the size of the platform's word: 64 bits on a 64-bit platform, as Int.bitWidth printed, and 32 bits on a 32-bit one.

  2. Question 2 of 14 What happens with this code?

    Read the code, then choose one answer.

    let a: UInt8 = 200
    let b = 300
    print(a < b)
    Show the answer: Question 2 of 14

    Answer: It prints true

    Comparisons work across integer types and arithmetic does not: a < b prints true, as seat < count + 1 did in limits.swift, while a + b would not compile.

  3. Question 3 of 14 A data file stores each sensor reading as a signed 16-bit number. Which type should hold a reading as it is read from the file?

    Choose one answer.

    Show the answer: Question 3 of 14

    Answer: Int16, converted to Int where the program calculates with it

    A fixed-size format is the case for a sized type: Int16 matches the file exactly. Convert to Int once, where the program does arithmetic, and the rest of the code needs no conversions.

  4. Question 4 of 14 In a program built with -O, an addition of two Int values read at run time gives a result larger than Int.max. What happens?

    Choose one answer.

    Show the answer: Question 4 of 14

    Answer: The program stops with a trap at that line

    A checked build, -Onone or -O, traps on the first result that does not fit. The program ends there, and the lines after it never run.

  5. Question 5 of 14 A team builds a service with -Ounchecked to make it faster. What changes when a counter goes past its type's maximum?

    Choose one answer.

    Show the answer: Question 5 of 14

    Answer: The service goes on with a wrong value and reports nothing

    -Ounchecked turns an overflow from a trap into a wrong value that the service keeps using. The speed it gains comes from leaving out exactly the checks that would have found the bug.

  6. Question 6 of 14 What is UInt8.max &+ 1?

    Type a number.

    Show the answer: Question 6 of 14

    Answer: 0

    UInt8.max is 255, eight bits that are all 1. Adding 1 needs a ninth bit, which &+ drops, so the result is 0, as wrapping.swift printed.

  7. Question 7 of 14 What is Int8.min &- 1?

    Type a number.

    Show the answer: Question 7 of 14

    Answer: 127

    Int8.min is −128. Going one below the minimum wraps round to the maximum, 127, because two's complement keeps the range in a circle of 256 values.

  8. Question 8 of 14 A function adds two amounts in paise and must report "too large" instead of stopping the program. Which code does that?

    Choose one answer.

    Show the answer: Question 8 of 14

    Answer: let r = a.addingReportingOverflow(b), then report if r.overflow is true, else use r.partialValue

    Use a reporting method when overflow is possible and must be handled: addingReportingOverflow(_:) returns the wrapped value and a flag, as order_total.swift does for each step of its order total.

  9. Question 9 of 14 What does UInt8(exactly: 300) give?

    Choose one answer.

    Show the answer: Question 9 of 14

    Answer: nil

    init(exactly:) succeeds only when the value fits exactly, and gives nil otherwise, which if let can check, as narrowing.swift did.

  10. Question 10 of 14 What is UInt8(truncatingIfNeeded: 260)?

    Type a number.

    A hint

    Keep the low eight bits, which is the same as subtracting 256 as often as needed.

    Show the answer: Question 10 of 14

    Answer: 4

    260 is 1 0000 0100 in binary. The low eight bits, 0000 0100, are 4, the same as 260 − 256.

  11. Question 11 of 14 What does Int("1_000") give?

    Choose one answer.

    Show the answer: Question 11 of 14

    Answer: nil

    Int("…") accepts an optional sign and digits, nothing else, so "1_000" gives nil, as narrowing.swift printed. The literal 1_000 in code is 1000.

  12. Question 12 of 14 What is -9 / 4?

    Type a number.

    Show the answer: Question 12 of 14

    Answer: -2

    The exact quotient is −2.25, and integer division cuts the fraction off towards zero, so the result is −2, not −3.

  13. Question 13 of 14 Days are numbered 0 (Monday) to 6 (Sunday), and today is 2, Wednesday. Which expression gives the day 5 days earlier, 4 (Friday)?

    Choose one answer.

    Show the answer: Question 13 of 14

    Answer: ((2 - 5) % 7 + 7) % 7

    Swift's % is a remainder that takes the sign of its left side, so a negative day stays negative. Adding the divisor and taking the remainder again maps it into 0 to 6: ((−3) + 7) % 7 = 4.

  14. Question 14 of 14 What does division.swift print?

    What does this program print? Choose one answer.

    // 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)
    Show the answer: Question 14 of 14

    Answer: it prints

    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

    Integer division cuts towards zero, % keeps the sign of its left side, and (a / b) * b + a % b always gives a back, so the bill check prints 1000.

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: Int is 64 bits on this Mac and follows the platform, the sized types from Int8 to Int64 keep their size everywhere, and Int128 and UInt128 arrived in Swift 6.0; use Int unless 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 -Onone and -O builds alike; only -Ounchecked removes 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 the ReportingOverflow methods return the wrapped value with a flag that says whether it overflowed.
  • init(exactly:) gives nil, init(clamping:) the nearest value and init(truncatingIfNeeded:) the low bits, while a plain conversion that does not fit traps; Int("…") gives nil instead 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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.