Sla grove locaties afzonderlijk van lokale offsets op voor een Mac-canvas dat zich uitstrekt over enorme coördinaten en piepkleine details. Trek de oorsprong van het aanzicht af voordat je zoom toepast, en controleer de precisie bij elke conversie. In deze tutorial bouw je die berekening op met synthetische scènegegevens.
De concrete taak is om een synthetische markering op een inzoombaar desktopcanvas te positioneren zonder dat de kleine verplaatsing verloren gaat doordat het totale scèneadres groot is. Het voorbeeld behandelt één as; pas dezelfde berekening afzonderlijk toe op de andere as. Het levert numerieke tekencoördinaten op, zodat je de rekenstappen kunt inspecteren voordat je ze aan een canvas koppelt.
Waarom verdwijnt een kleine verplaatsing bij een grote coördinaat?
Swift’s Double is een 64-bits drijvendekommagetaltype. Het bereik omvat grote en kleine magnitudes, maar dat betekent niet dat het elke kleine toename bij een grote waarde kan behouden. Apple documenteert het type en de conversie-API’s in Double.
Binaire dubbele precisie heeft 53 significante binaire cijfers. Daardoor neemt de afstand tussen representeerbare waarden toe met de magnitude. Goldbergs referentie over rekenkunde legt zowel deze representatie uit als hoe het aftrekken van eerder afgeronde operanden informatieverlies uit eerdere stappen zichtbaar kan maken. What Every Computer Scientist Should Know About Floating-Point Arithmetic
Neem deze uitdrukkelijk illustratieve invoerwaarden: een celindex van 2^40, een celbreedte van 1024 scène-eenheden en een markeringsoffset van 2^-30 scène-eenheden. Door het celadres en de breedte te combineren ontstaat een absolute coördinaat van 2^50. Bij die positieve coördinaat liggen aangrenzende Double-waarden 0.25 eenheden uit elkaar. Het optellen van de kleine offset wordt afgerond naar de oorspronkelijke coördinaat.
Daarom levert het achteraf aftrekken van de oorsprong nul op. Die nul vermenigvuldigen met een grote zoomfactor kan de offset niet terughalen. De precisie ging verloren toen de absolute coördinaat werd samengesteld.
Gebruik x.ulp om de afstand tussen waarden te bekijken bij de magnitude die je daadwerkelijk opslaat. Apple definieert dit als de eenheid op de laatste positie en documenteert de uitzonderingen, waaronder niet-eindige waarden. Double.ulpOfOne beschrijft de afstand bij één; het is geen universele absolute tolerantie voor scènecoördinaten. ulp
Wat moet de coördinaatrepresentatie behouden?
Geef voor dit canvas elke as weer met een Int64-celindex en een lokale Double-offset. Een positie betekent conceptueel cell × cellSize + local, maar de berekening moet voorkomen dat die absolute drijvendekommawaarde wordt samengesteld.
Houd de markering en de oorsprong van het aanzicht vanaf het begin in deze gesplitste vorm. Een al afgeronde absolute coördinaat omzetten naar een cel en offset kan ontbrekende details niet terughalen.
De volgende punten zijn ontwerpkeuzes voor het uitgewerkte voorbeeld, geen universele numerieke limieten:
- Elke cel is exact
1024scène-eenheden breed. - Celindices gebruiken
Int64; het zijn adressen of aantallen, geen drijvendekommagetallen. - Elke lokale offset is eindig en ligt in
0..<1024. - Gedetailleerde positionering accepteert alleen de oorsprongscel en de direct aangrenzende cellen.
- Zoom is een eindig, positief aantal weergave-eenheden per scène-eenheid.
- De schermoorsprong is de synthetische tekencoördinaat
512.
Bereken de relatieve coördinaat als cellDifference × cellSize + localDifference. Trek de gehele celindices cruciaal genoeg van elkaar af voordat je het verschil naar Double converteert. De gewone gehele conversie van Apple kan afronden; Double(exactly:) geeft daarentegen een fout als exacte representatie niet mogelijk is. Double
Zelfs het aftrekken van twee geldige Int64-waarden kan het bereik van dat type overschrijden. Gebruik subtractingReportingOverflow(_:) en controleer de vlag voordat je het resultaat gebruikt. Apple documenteert uitdrukkelijk dat het deelresultaat wordt afgekapt wanneer er overflow optreedt. subtractingReportingOverflow(_:)
Deze indeling behoudt een groot adres onafhankelijk van de lokale details. De precisie blijft wel afhankelijk van de lokale offsets: een heel kleine toename kan verdwijnen wanneer die bij een voldoende grote lokale offset wordt opgeteld. Kies celbreedtes en lokale ankerpunten op basis van het kleinste detail dat de huidige weergave nodig heeft.
Voer een volledige lokale berekening uit op je Mac
Je hebt een werkende Swift-toolchain nodig. Volg indien nodig de actuele instructies voor Install Swift — macOS. Controleer in Terminal de installatie en maak een klein uitvoerbaar pakket volgens de workflow in Build a Command-line Tool.
swift --version
mkdir SceneCoordinates
cd SceneCoordinates
swift package init --name SceneCoordinates --type executable
Vervang in de gegenereerde uitvoerbare target onder Sources het Swift-bestand met het entrypoint door het volledige programma hieronder. Gebruik één entrypointbestand met dit programma en zonder aanvullende top-level statements. Het importeert alleen de standaardbibliotheek van Swift en vereist geen pakketafhankelijkheden.
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)
}
}
Voer het pakket uit vanuit de bijbehorende map:
swift run SceneCoordinates
De verwachte numerieke resultaten zijn respectievelijk 0.25, 0.0, 513.0 en 513.0. Dit zijn afgeleide resultaten voor de opgegeven synthetische invoerwaarden. De onderliggende binary64-afstand tussen waarden, de verdwenen verplaatsing en de schaling met machten van twee zijn met Python gecontroleerd in de onderzoeksomgeving; daar was geen Swift-compiler beschikbaar, dus het Swift-programma is geïnspecteerd maar niet uitgevoerd.
Het eerste stabiele resultaat behoudt 2^-30 afzonderlijk van het enorme celadres en schaalt het vervolgens met 2^30 naar één weergave-eenheid. Het tweede gebruikt 2^-600 met een zoomfactor van 2^600; beide invoerwaarden en hun product zijn in dit voorbeeld representeerbaar. Een kleine magnitude betekent op zichzelf geen verlies van precisie.
Het programma gebruikt a.addingProduct(b, c) voor a + b × c. Apple documenteert dit als een gefuseerde bewerking zonder tussenliggende afronding. Dit vermindert afronding binnen die bewerking; de lokale aftrekking en eventuele eerdere afronding van invoerwaarden blijven afzonderlijke stappen. addingProduct(::)
Hoe kies je een bruikbaar precisiebudget?
Begin met de kleinste afstand die bij de huidige zoom zichtbaar moet blijven. Deel voor een illustratief doel van een kwart weergave-eenheid 0.25 door de zoomfactor om een foutbudget in scène-eenheden te krijgen. Controleer vervolgens de afstand tussen waarden bij de lokale coördinaten en tussenresultaten die in de berekening worden gebruikt.
Bij zoomfactor 2^30 is het illustratieve budget bijvoorbeeld 2^-32 scène-eenheden. Bij lokale coördinaat 512 is de afstand tussen Double-waarden 2^-43, aanzienlijk kleiner dan dat budget. Bij de absolute coördinaat 2^50 is de afstand 0.25, veel te grof. Deze berekende waarden verklaren waarom een nabij lokaal ankerpunt voor deze weergave helpt.
De afstand tussen waarden is een diagnostisch hulpmiddel, geen volledige foutgrens. Conversie van invoerwaarden, aftrekkingen, transformaties en de uiteindelijke tekenconversie kunnen elk fouten toevoegen. Test bekende verplaatsingen via het volledige traject, inclusief de daadwerkelijke renderer, voordat je een zoombereik accepteert.
| Waarneming | Waarschijnlijk numeriek probleem | Volgende stap |
|---|---|---|
| Een offset optellen verandert de opgeslagen coördinaat niet | De opslagafstand tussen waarden is groter dan de toename | Behoud de offset afzonderlijk of kies een dichterbij gelegen ankerpunt |
| Relatieve coördinaten werken tot aan de conversie | De precisie van de bestemming is onvoldoende | Converteer na het aftrekken van de oorsprong en toepassen van de zoom; controleer de resulterende fout |
| Waarden worden oneindig | Een bewerking overschrijdt het eindige bereik | Verander de volgorde van bewerkingen of beperk het ondersteunde aanzicht |
| Een niet-nul verplaatsing wordt nul | Underflow of afronding heeft de waarde uitgewist | Schaal eerder om of verander de lokale representatie |
| Een verre cel wordt door dit programma afgewezen | Het beleid voor de gedetailleerde weergave is overschreden | Kies een nabijgelegen oorsprong of gebruik een afzonderlijke grove berekening voor het overzicht |
In een overzicht kan een klein detail terecht worden weergegeven als veel minder dan één weergave-eenheid. Leg dat gedrag expliciet vast. De beperking tot aangrenzende cellen in het voorbeeld begrenst bewust de gedetailleerde berekeningen; de beperking verruimen vereist een nieuw precisiebudget in plaats van simpelweg de controle te verwijderen.
Wat gebeurt er bij zeer kleine schalen en tijdens het tekenen?
Apple maakt onderscheid tussen leastNormalMagnitude en kleinere positieve subnormale waarden. Subnormale waarden hebben minder precisie dan normale waarden, dus het kleinste positieve getal is geen goede standaardwerkschaal. Controleer ook tussenresultaten wanneer je kleine offsets met de zoomfactor vermenigvuldigt. leastNormalMagnitude
Het voorbeeld wijst een niet-nul verplaatsing af die bij het schalen nul wordt of niet meer te onderscheiden is van de schermoorsprong. Het garandeert niet dat elk niet-nul resultaat binnen een visuele tolerantie valt. Voeg een tolerantiecontrole toe die past bij je canvas en handel opgeworpen fouten expliciet af in plaats van de oorsprongscoördinaat in te vullen.
Behoud voor een tweedimensionaal canvas een gesplitste positie voor elke as en bereken beide relatieve coördinaten voordat je tekent. Voor een uitbreiding naar drie dimensies biedt Apple simd_double3, een alias van SIMD3<Double>. Elk onderdeel blijft een 64-bits drijvendekommagetal met een eigen precisielimiet. simd_double3
Houd de gesplitste scènetoestand aan als de gezaghebbende positie. Converteer alleen de uiteindelijke nabije tekencoördinaten naar het type dat je teken-API vereist, en meet de conversiefout aan de hand van het foutbudget in weergaveruimte. Als die uiteindelijke waarden nog steeds enorm zijn, bepaal dan eerst of het object in het huidige aanzicht thuishoort.
Een stabiel canvas vereist een vastgelegd ondersteund bereik: gehele celadressen, begrensde lokale offsets, een zoominterval en een aanvaardbare fout in de weergaveruimte. Behoud details voordat je grote waarden samenstelt, bereken relatief ten opzichte van een nabijgelegen oorsprong en controleer representatieve verplaatsingen aan de grenzen van dat bereik.
Bronnen
- Double — Apple Developer Documentation
- ulp — Apple Developer Documentation
- subtractingReportingOverflow(_:) — Apple Developer Documentation
- addingProduct(::) — Apple Developer Documentation
- leastNormalMagnitude — Apple Developer Documentation
- simd_double3 — Apple Developer Documentation
- Install Swift — Swift.org
- Build a Command-line Tool — Swift.org
- What Every Computer Scientist Should Know About Floating-Point Arithmetic — Oracle