For a Mac canvas that spans huge coordinates and tiny details, store coarse locations separately from local offsets. Subtract the view origin before applying zoom, and check precision at each conversion. This tutorial builds that calculation using synthetic scene data.

The concrete task is positioning a synthetic marker on a zoomable desktop canvas without losing its small displacement because its overall scene address is large. The example handles one axis; apply the same calculation independently to the other axis. It produces numeric drawing coordinates so you can inspect the arithmetic before connecting it to a canvas.

Why does a tiny movement disappear at a large coordinate?

Swift’s Double is a 64-bit floating-point type. Its range allows large and small magnitudes, but that does not mean it can retain every tiny increment added to a large value. Apple documents the type and its conversion APIs in Double.

Binary double precision has 53 significant binary digits. Consequently, spacing between representable values grows with magnitude. Goldberg’s arithmetic reference explains both this representation and how subtracting previously rounded operands can expose information lost earlier. What Every Computer Scientist Should Know About Floating-Point Arithmetic

Consider these explicitly illustrative inputs: a cell index of 2^40, a cell width of 1024 scene units, and a marker offset of 2^-30 scene units. Combining the cell address and width produces an absolute coordinate of 2^50. At that positive coordinate, adjacent Double values are 0.25 units apart. Adding the tiny offset rounds back to the original coordinate.

Subtracting the origin afterward therefore gives zero. Multiplying that zero by a large zoom factor cannot recover the offset. The loss occurred when the absolute coordinate was assembled.

Use x.ulp to inspect spacing at the magnitude you actually store. Apple defines this as the unit in the last place and documents its exceptions, including nonfinite values. Double.ulpOfOne describes spacing at one; it is not a universal absolute tolerance for scene coordinates. ulp

What should the coordinate representation preserve?

For this canvas, represent each axis with an Int64 cell index and a Double local offset. A position conceptually means cell × cellSize + local, but the calculation should avoid constructing that absolute floating-point value.

Keep the marker and view origin in this split form from the beginning. Converting an already rounded absolute coordinate into a cell and offset cannot recreate missing detail.

The following are design assumptions for the worked example, not universal numeric limits:

  • Each cell is exactly 1024 scene units wide.
  • Cell indices use Int64; they are addresses or counts, not floating-point quantities.
  • Each local offset is finite and lies in 0..<1024.
  • Detailed positioning accepts only the origin cell and its immediate neighbors.
  • Zoom is a finite, positive number of display units per scene unit.
  • The screen origin is the synthetic drawing coordinate 512.

Compute the relative coordinate as cellDifference × cellSize + localDifference. Crucially, subtract the integer cell indices before converting the difference to Double. Apple’s ordinary integer conversion may round; Double(exactly:) instead fails when exact representation is unavailable. Double

Even subtracting two valid Int64 values can exceed that type’s range. Use subtractingReportingOverflow(_:) and inspect its flag before using the result. Apple explicitly documents that the partial result is truncated when overflow occurs. subtractingReportingOverflow(_:)

This layout preserves a large address independently of local detail. Its precision still depends on the local offsets: a very tiny increment can disappear when added to a sufficiently large local offset. Choose cell widths and local anchors according to the smallest detail the current view needs.

Run a complete local calculation on your Mac

You need a working Swift toolchain. Follow the current Install Swift — macOS instructions if necessary. In Terminal, check the installation and create a small executable package using the workflow documented in Build a Command-line Tool.

swift --version
mkdir SceneCoordinates
cd SceneCoordinates
swift package init --name SceneCoordinates --type executable

In the generated executable target under Sources, replace its entry-point Swift file with the complete program below. Use one entry-point file containing this program, with no additional top-level statements. It imports only Swift’s standard library and requires no package dependencies.

import Swift

struct AxisPosition {
    let cell: Int64
    let local: Double
}

enum CoordinateError: Error {
    case invalidInput
    case cellDifferenceOverflow
    case outsideDetailedView
    case inexactCellConversion
    case unrepresentableResult
}

func screenCoordinate(
    point: AxisPosition,
    origin: AxisPosition,
    zoom: Double
) throws -> Double {
    let cellSize = 1024.0
    let screenOrigin = 512.0

    guard point.local.isFinite,
          origin.local.isFinite,
          point.local >= 0, point.local < cellSize,
          origin.local >= 0, origin.local < cellSize,
          zoom.isFinite, zoom > 0 else {
        throw CoordinateError.invalidInput
    }

    let difference = point.cell.subtractingReportingOverflow(origin.cell)
    guard !difference.overflow else {
        throw CoordinateError.cellDifferenceOverflow
    }
    guard (-1...1).contains(difference.partialValue) else {
        throw CoordinateError.outsideDetailedView
    }
    guard let cells = Double(exactly: difference.partialValue) else {
        throw CoordinateError.inexactCellConversion
    }

    let localDifference = point.local - origin.local
    let delta = localDifference.addingProduct(cells, cellSize)
    let scaled = delta * zoom
    guard delta.isFinite, scaled.isFinite,
          delta == 0 || scaled != 0 else {
        throw CoordinateError.unrepresentableResult
    }

    let screen = screenOrigin.addingProduct(delta, zoom)
    guard screen.isFinite,
          delta == 0 || screen != screenOrigin else {
        throw CoordinateError.unrepresentableResult
    }
    return screen
}

@main
struct SceneCoordinates {
    static func main() throws {
        let cell: Int64 = 1_099_511_627_776
        let step = Double(sign: .plus, exponent: -30, significand: 1)
        let zoom = Double(sign: .plus, exponent: 30, significand: 1)
        let absoluteBase = Double(cell) * 1024.0
        let collapsed = (absoluteBase + step) - absoluteBase

        let screen = try screenCoordinate(
            point: AxisPosition(cell: cell, local: step),
            origin: AxisPosition(cell: cell, local: 0),
            zoom: zoom
        )

        let tiny = Double(sign: .plus, exponent: -600, significand: 1)
        let largeZoom = Double(sign: .plus, exponent: 600, significand: 1)
        let tinyScreen = try screenCoordinate(
            point: AxisPosition(cell: 0, local: tiny),
            origin: AxisPosition(cell: 0, local: 0),
            zoom: largeZoom
        )

        precondition(absoluteBase.ulp == 0.25)
        precondition(collapsed == 0)
        precondition(screen == 513)
        precondition(tinyScreen == 513)

        print("Absolute spacing:", absoluteBase.ulp)
        print("Absolute displacement:", collapsed)
        print("Split-coordinate screen:", screen)
        print("Tiny-coordinate screen:", tinyScreen)
    }
}

Run the package from its directory:

swift run SceneCoordinates

The expected numeric results are 0.25, 0.0, 513.0, and 513.0, respectively. These are derived results for the supplied synthetic inputs. The underlying binary64 spacing, collapsed displacement, and power-of-two scaling were checked with Python in the research environment; a Swift compiler was unavailable there, so the Swift program was inspected but not executed.

The first stable result preserves 2^-30 separately from the huge cell address, then scales it by 2^30 to produce one display unit. The second uses 2^-600 with a zoom of 2^600; both inputs and their product are representable for this example. Small magnitude alone does not imply lost precision.

The program uses a.addingProduct(b, c) for a + b × c. Apple documents this as a fused operation without intermediate rounding. It reduces rounding within that operation; the local subtraction and any earlier input rounding remain separate. addingProduct(::)

How do you choose a useful precision budget?

Start with the smallest separation that must remain visible at the current zoom. For an illustrative target of one quarter of a display unit, divide 0.25 by zoom to obtain a scene-space error budget. Then inspect spacing at the local coordinates and intermediate values used by the calculation.

For example, at zoom 2^30, the illustrative budget is 2^-32 scene units. At local coordinate 512, Double spacing is 2^-43, substantially finer than that budget. At the absolute coordinate 2^50, spacing is 0.25, far too coarse. These calculated values explain why a nearby local anchor helps this particular view.

Spacing is a diagnostic, not a complete error bound. Input conversion, subtraction, transforms, and final drawing conversion can each contribute error. Test known displacements through the complete path, including the actual renderer, before accepting a zoom range.

Observation Likely numeric issue Next action
Adding an offset leaves the stored coordinate unchanged Storage spacing exceeds the increment Preserve the offset separately or choose a closer anchor
Relative coordinates work until conversion Destination precision is insufficient Convert after origin subtraction and zoom; check the resulting error
Values become infinite An operation exceeds finite range Change evaluation order or constrain the supported view
A nonzero displacement becomes zero Underflow or rounding erased it Rescale earlier or change the local representation
A distant cell is rejected by this program The detailed-view policy was exceeded Select a nearby origin or use a separate coarse overview calculation

For an overview, some tiny detail may legitimately map to much less than a display unit. Define that behavior explicitly. The sample’s neighboring-cell restriction intentionally bounds detailed arithmetic; widening it requires a new precision budget rather than simply deleting the guard.

What happens at very small scales and during drawing?

Apple distinguishes leastNormalMagnitude from smaller positive subnormal values. Subnormals have reduced precision relative to normal values, so the smallest positive number is not a good default working scale. Check intermediate results as well as inputs when multiplying tiny offsets by zoom. leastNormalMagnitude

The sample rejects a nonzero displacement that scales to zero or becomes indistinguishable from the screen origin. It does not certify every nonzero result against a visual tolerance. Add a tolerance-based acceptance check appropriate to your canvas, and handle thrown errors explicitly instead of substituting the origin coordinate.

For a two-dimensional canvas, preserve a split position for each axis and calculate both relative coordinates before drawing. For a three-dimensional extension, Apple provides simd_double3, an alias of SIMD3<Double>. Each component remains a 64-bit floating-point value with its own precision limit. simd_double3

Keep the split scene state as the authoritative position. Convert only the final nearby drawing coordinates to the type required by your drawing API, and measure conversion error against the display-space budget. If those final values are still huge, first determine whether the object belongs in the current viewport.

A stable canvas needs a declared supported range: integer cell addresses, bounded local offsets, a zoom interval, and an acceptable display-space error. Preserve detail before assembling large values, calculate relative to a nearby origin, and verify representative displacements at the limits of that range.

Sources