Pour un canevas Mac couvrant de très grandes coordonnées et de minuscules détails, stockez séparément les positions grossières et les décalages locaux. Soustrayez l’origine de la vue avant d’appliquer le zoom et vérifiez la précision à chaque conversion. Ce tutoriel construit ce calcul à partir de données de scène synthétiques.

La tâche concrète consiste à positionner un marqueur synthétique sur un canevas de bureau zoomable sans perdre son faible déplacement à cause de l’adresse globale élevée de la scène. L’exemple traite un axe ; appliquez le même calcul indépendamment à l’autre axe. Il produit des coordonnées de dessin numériques afin que vous puissiez examiner les opérations avant de les relier à un canevas.

Pourquoi un petit déplacement disparaît-il à une grande coordonnée ?

En Swift, Double est un type à virgule flottante sur 64 bits. Sa plage permet de représenter de grandes et de petites magnitudes, mais cela ne signifie pas qu’il peut conserver chaque minuscule incrément ajouté à une grande valeur. Apple documente ce type et ses API de conversion dans Double.

La précision binaire double comporte 53 chiffres binaires significatifs. Par conséquent, l’écart entre les valeurs représentables augmente avec la magnitude. La référence de Goldberg sur l’arithmétique explique à la fois cette représentation et comment la soustraction d’opérandes déjà arrondis peut révéler des informations perdues auparavant. Ce que tout informaticien devrait savoir sur l’arithmétique à virgule flottante

Considérez ces entrées explicitement illustratives : un indice de cellule de 2^40, une largeur de cellule de 1024 unités de scène et un décalage de marqueur de 2^-30 unités de scène. La combinaison de l’adresse de cellule et de la largeur produit une coordonnée absolue de 2^50. À cette coordonnée positive, les valeurs Double adjacentes sont espacées de 0.25 unités. L’ajout du minuscule décalage est arrondi à la coordonnée d’origine.

La soustraction de l’origine ensuite donne donc zéro. Multiplier ce zéro par un facteur de zoom élevé ne peut pas rétablir le décalage. La perte a eu lieu lors de la construction de la coordonnée absolue.

Utilisez x.ulp pour examiner l’espacement à la magnitude que vous stockez réellement. Apple définit cette valeur comme l’unité du dernier rang et documente ses exceptions, notamment les valeurs non finies. Double.ulpOfOne décrit l’espacement à la valeur un ; ce n’est pas une tolérance absolue universelle pour les coordonnées de scène. ulp

Que doit préserver la représentation des coordonnées ?

Pour ce canevas, représentez chaque axe avec un indice de cellule Int64 et un décalage local Double. Une position signifie conceptuellement cell × cellSize + local, mais le calcul doit éviter de construire cette valeur absolue à virgule flottante.

Conservez dès le départ le marqueur et l’origine de la vue sous cette forme fractionnée. Convertir une coordonnée absolue déjà arrondie en cellule et décalage ne peut pas recréer les détails manquants.

Les points suivants sont des hypothèses de conception pour l’exemple, et non des limites numériques universelles :

  • Chaque cellule mesure exactement 1024 unités de scène.
  • Les indices de cellule utilisent Int64 ; ce sont des adresses ou des nombres, pas des quantités à virgule flottante.
  • Chaque décalage local est fini et appartient à 0..<1024.
  • Le positionnement détaillé n’accepte que la cellule d’origine et ses voisines immédiates.
  • Le zoom est un nombre fini et positif d’unités d’affichage par unité de scène.
  • L’origine de l’écran est la coordonnée de dessin synthétique 512.

Calculez la coordonnée relative comme cellDifference × cellSize + localDifference. Point crucial : soustrayez les indices de cellule entiers avant de convertir la différence en Double. La conversion entière ordinaire d’Apple peut arrondir ; Double(exactly:) échoue plutôt lorsque la représentation exacte est impossible. Double

Même la soustraction de deux valeurs Int64 valides peut dépasser la plage de ce type. Utilisez subtractingReportingOverflow(_:) et examinez son indicateur avant d’utiliser le résultat. Apple précise que le résultat partiel est tronqué en cas de débordement. subtractingReportingOverflow(_:)

Cette disposition préserve une grande adresse indépendamment des détails locaux. Sa précision dépend toujours des décalages locaux : un incrément très faible peut disparaître lorsqu’il est ajouté à un décalage local suffisamment grand. Choisissez la largeur des cellules et les ancres locales en fonction du plus petit détail requis par la vue actuelle.

Effectuez un calcul local complet sur votre Mac

Vous avez besoin d’une chaîne d’outils Swift fonctionnelle. Suivez au besoin les instructions actuelles Installer Swift — macOS. Dans Terminal, vérifiez l’installation et créez un petit paquet exécutable en suivant le processus documenté dans Créer un outil en ligne de commande.

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

Dans la cible exécutable générée sous Sources, remplacez son fichier Swift de point d’entrée par le programme complet ci-dessous. Utilisez un seul fichier de point d’entrée contenant ce programme, sans instruction de niveau supérieur supplémentaire. Il importe uniquement la bibliothèque standard Swift et ne nécessite aucune dépendance de paquet.

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

Exécutez le paquet depuis son répertoire :

swift run SceneCoordinates

Les résultats numériques attendus sont respectivement 0.25, 0.0, 513.0 et 513.0. Ce sont des résultats dérivés des entrées synthétiques fournies. L’espacement binaire64 sous-jacent, le déplacement annulé et la mise à l’échelle par puissances de deux ont été vérifiés avec Python dans l’environnement de recherche ; aucun compilateur Swift n’y était disponible, le programme Swift a donc été examiné sans être exécuté.

Le premier résultat stable préserve 2^-30 séparément de l’énorme adresse de cellule, puis le multiplie par 2^30 pour obtenir une unité d’affichage. Le second utilise 2^-600 avec un zoom de 2^600 ; dans cet exemple, les deux entrées et leur produit sont représentables. Une faible magnitude n’implique pas à elle seule une perte de précision.

Le programme utilise a.addingProduct(b, c) pour a + b × c. Apple décrit cette opération comme une opération fusionnée sans arrondi intermédiaire. Elle réduit l’arrondi au cours de cette opération ; la soustraction locale et tout arrondi antérieur des entrées restent distincts. addingProduct(::)

Comment choisir un budget de précision utile ?

Commencez par le plus petit écart qui doit rester visible au niveau de zoom actuel. Pour une cible illustrative d’un quart d’unité d’affichage, divisez 0.25 par le zoom pour obtenir un budget d’erreur dans l’espace de la scène. Examinez ensuite l’espacement aux coordonnées locales et aux valeurs intermédiaires utilisées par le calcul.

Par exemple, avec un zoom de 2^30, le budget illustratif est de 2^-32 unités de scène. À la coordonnée locale 512, l’espacement de Double est de 2^-43, nettement plus fin que ce budget. À la coordonnée absolue 2^50, l’espacement est de 0.25, bien trop grossier. Ces valeurs calculées expliquent pourquoi une ancre locale proche aide cette vue particulière.

L’espacement est un indicateur de diagnostic, pas une borne d’erreur complète. La conversion des entrées, la soustraction, les transformations et la conversion finale pour le dessin peuvent chacune ajouter une erreur. Faites passer des déplacements connus par l’ensemble du parcours, y compris le moteur de rendu réel, avant d’accepter une plage de zoom.

Observation Problème numérique probable Action suivante
L’ajout d’un décalage ne modifie pas la coordonnée stockée L’espacement du stockage dépasse l’incrément Préservez le décalage séparément ou choisissez une ancre plus proche
Les coordonnées relatives fonctionnent jusqu’à la conversion La précision de destination est insuffisante Convertissez après la soustraction de l’origine et l’application du zoom ; vérifiez l’erreur obtenue
Les valeurs deviennent infinies Une opération dépasse la plage finie Modifiez l’ordre d’évaluation ou limitez la vue prise en charge
Un déplacement non nul devient nul Le sous-débordement ou l’arrondi l’a effacé Changez l’échelle plus tôt ou modifiez la représentation locale
Une cellule distante est rejetée par ce programme La politique de vue détaillée a été dépassée Choisissez une origine proche ou utilisez un calcul distinct et grossier pour la vue d’ensemble

Pour une vue d’ensemble, certains détails minuscules peuvent légitimement correspondre à bien moins d’une unité d’affichage. Définissez explicitement ce comportement. La restriction aux cellules voisines de l’exemple limite volontairement le calcul détaillé ; l’élargir exige un nouveau budget de précision, et non la simple suppression de la vérification.

Que se passe-t-il aux très petites échelles et pendant le dessin ?

Apple distingue leastNormalMagnitude des valeurs sous-normales positives plus petites. Les sous-normales ont une précision réduite par rapport aux valeurs normales ; le plus petit nombre positif ne constitue donc pas une bonne échelle de travail par défaut. Vérifiez les résultats intermédiaires ainsi que les entrées lorsque vous multipliez de minuscules décalages par le zoom. leastNormalMagnitude

L’exemple rejette un déplacement non nul qui, après mise à l’échelle, devient nul ou indiscernable de l’origine de l’écran. Il ne certifie pas que chaque résultat non nul respecte une tolérance visuelle. Ajoutez une vérification fondée sur une tolérance adaptée à votre canevas et gérez explicitement les erreurs levées au lieu de remplacer le résultat par la coordonnée de l’origine.

Pour un canevas bidimensionnel, préservez une position fractionnée pour chaque axe et calculez les deux coordonnées relatives avant le dessin. Pour une extension tridimensionnelle, Apple fournit simd_double3, un alias de SIMD3<Double>. Chaque composante reste une valeur à virgule flottante sur 64 bits, avec sa propre limite de précision. simd_double3

Conservez l’état de la scène fractionné comme position de référence. Convertissez uniquement les coordonnées finales proches destinées au dessin vers le type requis par votre API graphique, et mesurez l’erreur de conversion par rapport au budget dans l’espace d’affichage. Si ces valeurs finales sont encore énormes, déterminez d’abord si l’objet appartient à la fenêtre d’affichage actuelle.

Un canevas stable nécessite une plage prise en charge définie : des adresses de cellule entières, des décalages locaux bornés, un intervalle de zoom et une erreur acceptable dans l’espace d’affichage. Préservez les détails avant de construire de grandes valeurs, calculez par rapport à une origine proche et vérifiez des déplacements représentatifs aux limites de cette plage.

Sources