Per una canvas su Mac che copre coordinate enormi e dettagli minimi, memorizza separatamente le posizioni grossolane e gli offset locali. Sottrai l’origine della vista prima di applicare lo zoom e controlla la precisione a ogni conversione. Questo tutorial costruisce il calcolo usando dati di scena sintetici.

L’obiettivo concreto è posizionare un indicatore sintetico su una canvas desktop con zoom senza perdere il suo piccolo spostamento a causa dell’indirizzo complessivo molto grande della scena. L’esempio gestisce un asse; applica lo stesso calcolo indipendentemente all’altro asse. Produce coordinate numeriche di disegno, così puoi esaminare i calcoli prima di collegarli a una canvas.

Perché un piccolo movimento scompare a una coordinata grande?

Double di Swift è un tipo a virgola mobile a 64 bit. Il suo intervallo consente magnitudini grandi e piccole, ma ciò non significa che possa conservare ogni piccolo incremento aggiunto a un valore grande. Apple documenta il tipo e le sue API di conversione in Double.

La precisione doppia binaria ha 53 cifre binarie significative. Di conseguenza, la distanza tra valori rappresentabili cresce con la magnitudine. Il riferimento di Goldberg sull’aritmetica spiega sia questa rappresentazione sia il modo in cui sottrarre operandi già arrotondati può rivelare informazioni perse in precedenza. What Every Computer Scientist Should Know About Floating-Point Arithmetic

Considera questi input esplicitamente illustrativi: un indice di cella pari a 2^40, una larghezza della cella di 1024 unità di scena e un offset dell’indicatore di 2^-30 unità di scena. Combinando l’indirizzo della cella e la sua larghezza si ottiene una coordinata assoluta pari a 2^50. A questa coordinata positiva, i valori Double adiacenti distano 0.25 unità. L’aggiunta del piccolo offset viene arrotondata al valore originale della coordinata.

Sottrarre l’origine in seguito dà quindi zero. Moltiplicare quel valore zero per un fattore di zoom elevato non può recuperare l’offset. La perdita si è verificata quando è stata composta la coordinata assoluta.

Usa x.ulp per esaminare la distanza tra i valori alla magnitudine effettivamente memorizzata. Apple la definisce come unità nell’ultima posizione e ne documenta le eccezioni, compresi i valori non finiti. Double.ulpOfOne descrive la distanza a uno; non è una tolleranza assoluta universale per le coordinate di scena. ulp

Che cosa dovrebbe preservare la rappresentazione delle coordinate?

Per questa canvas, rappresenta ogni asse con un indice di cella Int64 e un offset locale Double. Concettualmente, una posizione significa cella × dimensioneCella + locale, ma il calcolo dovrebbe evitare di costruire quel valore assoluto in virgola mobile.

Mantieni l’indicatore e l’origine della vista in questa forma separata fin dall’inizio. Convertire una coordinata assoluta già arrotondata in una cella e un offset non può ricreare il dettaglio mancante.

Le seguenti sono ipotesi di progetto per l’esempio svolto, non limiti numerici universali:

  • Ogni cella è larga esattamente 1024 unità di scena.
  • Gli indici di cella usano Int64; sono indirizzi o conteggi, non quantità in virgola mobile.
  • Ogni offset locale è finito e rientra in 0..<1024.
  • Il posizionamento dettagliato accetta solo la cella d’origine e quelle immediatamente adiacenti.
  • Lo zoom è un numero finito e positivo di unità di visualizzazione per unità di scena.
  • L’origine dello schermo è la coordinata sintetica di disegno 512.

Calcola la coordinata relativa come differenzaCelle × dimensioneCella + differenzaLocale. È fondamentale sottrarre gli indici interi delle celle prima di convertire la differenza in Double. La conversione intera ordinaria di Apple può arrotondare; Double(exactly:) invece restituisce un errore quando la rappresentazione esatta non è disponibile. Double

Anche sottrarre due valori Int64 validi può superare l’intervallo di quel tipo. Usa subtractingReportingOverflow(_:) e controlla il relativo flag prima di usare il risultato. Apple documenta esplicitamente che, in caso di overflow, il risultato parziale viene troncato. subtractingReportingOverflow(_:)

Questa struttura preserva un indirizzo grande indipendentemente dal dettaglio locale. La precisione dipende comunque dagli offset locali: un incremento molto piccolo può scomparire quando viene aggiunto a un offset locale sufficientemente grande. Scegli le dimensioni delle celle e gli ancoraggi locali in base al dettaglio minimo richiesto dalla vista corrente.

Esegui un calcolo locale completo sul tuo Mac

Ti serve una toolchain Swift funzionante. Se necessario, segui le istruzioni aggiornate di Install Swift — macOS. In Terminal, controlla l’installazione e crea un piccolo pacchetto eseguibile seguendo la procedura documentata in Build a Command-line Tool.

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

Nel target eseguibile generato sotto Sources, sostituisci il file Swift di ingresso con il programma completo riportato di seguito. Usa un solo file di ingresso contenente questo programma, senza altre istruzioni al livello superiore. Importa solo la libreria standard di Swift e non richiede dipendenze del pacchetto.

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)
    }
}

Esegui il pacchetto dalla sua directory:

swift run SceneCoordinates

I risultati numerici attesi sono rispettivamente 0.25, 0.0, 513.0 e 513.0. Sono risultati derivati dagli input sintetici forniti. La distanza binaria64, lo spostamento collassato e la scalatura per potenze di due sono stati verificati con Python nell’ambiente di ricerca; lì non era disponibile un compilatore Swift, quindi il programma Swift è stato esaminato ma non eseguito.

Il primo risultato stabile conserva 2^-30 separatamente dal grande indirizzo della cella, poi lo scala di 2^30 per ottenere un’unità di visualizzazione. Il secondo usa 2^-600 con uno zoom di 2^600; per questo esempio, sia gli input sia il loro prodotto sono rappresentabili. Una magnitudine ridotta, da sola, non implica una perdita di precisione.

Il programma usa a.addingProduct(b, c) per a + b × c. Apple lo documenta come un’operazione fusa senza arrotondamento intermedio. Riduce l’arrotondamento all’interno di quell’operazione; la sottrazione locale e qualsiasi arrotondamento precedente degli input restano separati. addingProduct(::)

Come si sceglie un budget di precisione utile?

Parti dalla distanza minima che deve restare visibile allo zoom corrente. Per un obiettivo illustrativo di un quarto di unità di visualizzazione, dividi 0.25 per lo zoom per ottenere un budget di errore nello spazio di scena. Poi esamina la distanza tra i valori alle coordinate locali e ai valori intermedi usati nel calcolo.

Per esempio, con uno zoom 2^30, il budget illustrativo è 2^-32 unità di scena. Alla coordinata locale 512, la distanza tra i valori Double è 2^-43, decisamente inferiore a quel budget. Alla coordinata assoluta 2^50, la distanza è 0.25, decisamente troppo grande. Questi valori calcolati spiegano perché, per questa vista specifica, è utile un ancoraggio locale vicino.

La distanza tra i valori è uno strumento diagnostico, non un limite completo dell’errore. La conversione degli input, la sottrazione, le trasformazioni e la conversione finale per il disegno possono contribuire ciascuna all’errore. Prima di accettare un intervallo di zoom, prova spostamenti noti lungo l’intero percorso, compreso il renderer effettivo.

Osservazione Probabile problema numerico Azione successiva
L’aggiunta di un offset lascia invariata la coordinata memorizzata La distanza tra i valori memorizzati supera l’incremento Conserva l’offset separatamente oppure scegli un ancoraggio più vicino
Le coordinate relative funzionano fino alla conversione La precisione di destinazione è insufficiente Converti dopo aver sottratto l’origine e applicato lo zoom; controlla l’errore risultante
I valori diventano infiniti Un’operazione supera l’intervallo finito Modifica l’ordine di valutazione o limita la vista supportata
Uno spostamento diverso da zero diventa zero L’underflow o l’arrotondamento lo ha cancellato Riscalalo prima o modifica la rappresentazione locale
Il programma rifiuta una cella distante È stato superato il criterio della vista dettagliata Seleziona un’origine più vicina oppure usa un calcolo separato e approssimativo per la panoramica

Per una panoramica, alcuni dettagli minimi possono legittimamente corrispondere a molto meno di un’unità di visualizzazione. Definisci esplicitamente questo comportamento. La restrizione alle celle adiacenti nell’esempio limita intenzionalmente l’aritmetica dettagliata; ampliarla richiede un nuovo budget di precisione, non semplicemente la rimozione del controllo.

Che cosa succede alle scale molto piccole e durante il disegno?

Apple distingue leastNormalMagnitude dai valori subnormali positivi più piccoli. I subnormali hanno una precisione ridotta rispetto ai valori normali, quindi il numero positivo più piccolo non è una buona scala operativa predefinita. Controlla i risultati intermedi e gli input quando moltiplichi piccoli offset per lo zoom. leastNormalMagnitude

L’esempio rifiuta uno spostamento diverso da zero che, scalato, diventa zero o non è distinguibile dall’origine dello schermo. Non certifica ogni risultato diverso da zero rispetto a una tolleranza visiva. Aggiungi un controllo di accettazione basato sulla tolleranza adatto alla tua canvas e gestisci esplicitamente gli errori generati invece di sostituire il risultato con la coordinata d’origine.

Per una canvas bidimensionale, conserva una posizione separata per ogni asse e calcola entrambe le coordinate relative prima di disegnare. Per un’estensione tridimensionale, Apple fornisce simd_double3, un alias di SIMD3<Double>. Ogni componente resta un valore in virgola mobile a 64 bit con il proprio limite di precisione. simd_double3

Mantieni lo stato della scena in forma separata come posizione di riferimento. Converti solo le coordinate finali vicine per il disegno nel tipo richiesto dalla tua API di disegno e misura l’errore di conversione rispetto al budget nello spazio di visualizzazione. Se quei valori finali sono ancora enormi, determina prima se l’oggetto appartiene alla vista corrente.

Una canvas stabile richiede un intervallo supportato dichiarato: indirizzi di cella interi, offset locali limitati, un intervallo di zoom e un errore accettabile nello spazio di visualizzazione. Conserva i dettagli prima di comporre valori grandi, calcola rispetto a un’origine vicina e verifica spostamenti rappresentativi ai limiti di quell’intervallo.

Fonti