To turn a computed grid size into an integer count in Swift, choose the rounding rule first, then use Int(exactly:) to check representability. For a Mac canvas covered by tiles, round each axis upward and check the final multiplication for overflow.
This article develops that calculation for a desktop drawing tool: given a rectangular canvas and fixed-size tiles, determine the columns, rows, and total tiles needed to cover it. All coordinates and counts below are synthetic. The grid starts at the canvas’s own origin, uses no gaps, and permits partial tiles along the outer edges.
Choose the rounding rule from the grid’s purpose
A fractional grid dimension needs a policy before it needs a type conversion. If a canvas is 650 points wide and each tile is 64 points wide, the computed dimension is 650.0 / 64.0, or 10.15625. Covering the entire width requires 11 columns. Counting only complete tiles that fit within that width gives 10.
Swift provides explicit rounding rules for these different tasks. .up rounds toward positive infinity, .down toward negative infinity, and .towardZero removes the fractional part. Nearest rounding has separate rules for ties. These behaviors are defined in Apple’s FloatingPointRoundingRule documentation.
| Reader’s task | Rule for a nonnegative axis dimension | Result for 10.15625 |
|---|---|---|
| Cover the full extent, including an edge tile | .up |
11.0 |
| Count complete tiles fitting inside the extent | .down |
10.0 |
| Choose the nearest whole dimension | .toNearestOrAwayFromZero |
10.0 |
| Require the computed dimension to be integral already | No rounding; try Int(exactly:) directly |
nil |
For the coverage task, use .rounded(.up). Calling .rounded() without a rule selects nearest rounding with ties away from zero, which can leave part of this canvas uncovered. Also, rounding a Double returns an integral-valued Double; integer conversion remains a separate step. See Apple’s rounded(_:) documentation.
Round columns and rows separately
Suppose the synthetic canvas extends from horizontal coordinate 20 to 670 and vertical coordinate 30 to 440. Its width is 650 points and its height is 410 points. With square tiles measuring 64 points per side, the coverage calculation is:
- Columns:
ceil(650 / 64) = 11. - Rows:
ceil(410 / 64) = 7. - Total:
11 × 7 = 77tiles.
These are illustrative arithmetic results, derived from the stated layout. Dividing canvas area by tile area and rounding once gives 66, which does not describe the required rectangular arrangement. Each row needs 11 tile positions, and the canvas needs seven such rows. Preserve those two dimensions until both have been rounded.
This formula assumes that the first tile starts at the canvas origin. If tiles instead belong to a grid anchored elsewhere, the starting offset can change how many cells the rectangle intersects. Establish that alignment before applying a width-only calculation.
Why a direct integer conversion is insufficient
Swift’s ordinary floating-point-to-integer initializer rounds toward zero. Thus, Int(10.15625) produces 10. The source must also fit the destination integer type after truncation; Apple documents that an out-of-range conversion may cause a runtime error. See BinaryInteger.init(_:).
For a computed coverage dimension, the useful sequence is therefore Int(exactly: value.rounded(.up)), followed by optional handling. Apple documents that Int.init(exactly:) returns nil when the supplied floating-point value cannot be represented exactly as an integer.
The initializer answers a narrow question: does this supplied value fit exactly? It does not establish that the original geometry was valid, that the chosen rounding rule suits the layout, or that creating that many tiles is practical. Those are separate checks in the implementation below.
A complete Swift program for a bounded tile count
The following program uses Double for coordinates and dimensions and Int for counts. It requires finite, nonnegative extents; finite, positive tile dimensions; and a positive application-defined tile limit. A canvas with either extent equal to zero produces an empty grid. Reversed bounds are rejected rather than silently rearranged.
Save this complete program as GridCount.swift and run it with swift GridCount.swift on a Mac with a Swift toolchain available. It uses only the Swift standard library, so no imports are required. The program is presented with expected results; it was inspected but was not executed with a Swift compiler during this research.
enum GridError: Error {
case invalidInput
case unrepresentableAxis
case countOverflow
case exceedsLimit
}
struct GridCount {
let columns: Int
let rows: Int
let total: Int
}
func coveringAxis(extent: Double, tile: Double) throws -> Int {
guard extent.isFinite, extent >= 0,
tile.isFinite, tile > 0 else {
throw GridError.invalidInput
}
if extent == 0 { return 0 }
let quotient = extent / tile
guard quotient.isFinite, quotient > 0,
let count = Int(exactly: quotient.rounded(.up)),
count > 0 else {
throw GridError.unrepresentableAxis
}
return count
}
func coveringGrid(
width: Double,
height: Double,
tileWidth: Double,
tileHeight: Double,
maxCells: Int
) throws -> GridCount {
guard width.isFinite, width >= 0,
height.isFinite, height >= 0,
tileWidth.isFinite, tileWidth > 0,
tileHeight.isFinite, tileHeight > 0,
maxCells > 0 else {
throw GridError.invalidInput
}
if width == 0 || height == 0 {
return GridCount(columns: 0, rows: 0, total: 0)
}
let columns = try coveringAxis(extent: width, tile: tileWidth)
let rows = try coveringAxis(extent: height, tile: tileHeight)
let product = columns.multipliedReportingOverflow(by: rows)
guard !product.overflow else {
throw GridError.countOverflow
}
guard product.partialValue <= maxCells else {
throw GridError.exceedsLimit
}
return GridCount(
columns: columns,
rows: rows,
total: product.partialValue
)
}
let left: Double = 20
let right: Double = 670
let top: Double = 30
let bottom: Double = 440
do {
let grid = try coveringGrid(
width: right - left,
height: bottom - top,
tileWidth: 64,
tileHeight: 64,
maxCells: 10_000
)
print("columns=\(grid.columns), rows=\(grid.rows), total=\(grid.total)")
// Expected: columns=11, rows=7, total=77
} catch {
print("Grid calculation failed: \(error)")
}
What the checks accomplish
The finite-value checks reject NaN and infinity before either can become a count. Apple specifies that isFinite is false for both. Requiring positive tile dimensions also excludes division by zero and negative tile sizes.
Checking the quotient separately matters even after validating the inputs. The implementation rejects a nonfinite quotient, and it rejects zero when the extent was positive. That latter check makes an extremely small positive quotient that underflows to zero an explicit failure instead of an empty axis. This is a deliberate boundary of the example.
Each successful axis conversion still leaves the total to check. Two valid Int values can have a product outside the type’s range. Apple’s multipliedReportingOverflow(by:) documentation specifies that partialValue contains the complete product only when overflow is false. The program checks that flag before using the product.
The final maxCells comparison serves a different purpose: it enforces an application limit even when the arithmetic fits. The illustrative value of 10,000 is a caller-selected policy, not a recommendation about memory or drawing performance. Choose a limit based on what your application actually creates for each tile.
Avoid a floating-point range check at the integer boundary
A tempting check is value <= Double(Int.max) followed by Int(value). That comparison can be misleading because converting the integer bound to floating point may round it. Apple explicitly describes Double.init(_:) from Int as conversion to the closest possible representation.
On a 64-bit target, Int.max is 9_223_372_036_854_775_807. Its Double conversion rounds to 9_223_372_036_854_775_808, which is one larger. A floating-point value equal to that rounded bound would pass the comparison while remaining outside the integer range. Apple documents the relationship between platform width and integer size on its Int reference page.
This boundary example is an arithmetic illustration, not a proposed canvas size. It explains why the program asks Int(exactly:) to perform the representability check directly. It also avoids hard-coding a maximum count tied to a particular integer width.
Decide how to handle values near a tile boundary
Upward rounding follows the value actually stored. A quotient just above two requires three tiles under this coverage policy. Swift’s nextUp property provides the next greater representable floating-point value, so (2.0).nextUp.rounded(.up) is 3.0. The meaning of that adjacent value is documented in Apple’s nextUp reference.
Do not subtract an arbitrary epsilon merely to make a displayed value look integral. That changes the coverage policy and can remove a tile needed for a small real remainder. If the application intentionally snaps coordinates to a grid, define that snapping rule upstream and calculate counts from the snapped geometry. If dimensions originate as exact integer units, consider retaining those units through the count calculation.
The example also assumes that subtracting the supplied coordinates gives useful extents. An exact final conversion cannot recover detail already lost in earlier coordinate arithmetic. Keep coordinate magnitudes appropriate to the layout and apply the same units to canvas and tile dimensions.
Check the cases that distinguish correct behavior
Use boundary cases to verify your adaptation, especially if you change the rounding policy or empty-grid behavior. The following expected results follow from this program’s stated contract; they are not reported Swift test executions.
| Synthetic input | Expected outcome |
|---|---|
Canvas 650 × 410, tiles 64 × 64, limit 10_000 |
11 columns, 7 rows, 77 tiles |
Canvas 128 × 128, tiles 64 × 64, limit 10_000 |
2 columns, 2 rows, 4 tiles |
Canvas 129 × 128, tiles 64 × 64, limit 10_000 |
3 columns, 2 rows, 6 tiles |
| Either canvas extent is zero; other inputs valid | Empty grid with total zero |
| Negative extent, zero tile dimension, or nonfinite input | invalidInput |
| Positive extent divided by a tile produces zero or infinity | unrepresentableAxis |
An upward-rounded axis cannot fit in Int |
unrepresentableAxis |
| Valid axis counts whose product overflows | countOverflow |
The 77-tile example with limit 50 |
exceedsLimit |
For this Mac canvas task, keep geometry in floating point until each axis has been calculated, round upward to include partial edge tiles, and convert through Int(exactly:). Then validate multiplication and the application limit before creating the grid. That sequence turns the computed dimensions into a count with explicit, reviewable failure behavior.
Sources
- FloatingPointRoundingRule — Apple
- rounded(_:) — Apple
- BinaryInteger.init(_:) — Apple
- Int.init(exactly:) — Apple
- isFinite — Apple
- multipliedReportingOverflow(by:) — Apple
- Double.init(_:) — Apple
- Int — Apple
- nextUp — Apple