För en Mac-canvas som sträcker sig över enorma koordinater och små detaljer bör du lagra grova positioner separat från lokala förskjutningar. Subtrahera visningsområdets origo innan du tillämpar zoom och kontrollera precisionen vid varje konvertering. Den här handledningen bygger upp beräkningen med syntetiska scendata.
Den konkreta uppgiften är att positionera en syntetisk markör på en skrivbordscanvas med zoom utan att förlora dess lilla förskjutning på grund av att dess övergripande scenadress är stor. Exemplet hanterar en axel; tillämpa samma beräkning separat på den andra axeln. Det ger numeriska ritkoordinater så att du kan granska beräkningen innan du kopplar den till en canvas.
Varför försvinner en liten rörelse vid en stor koordinat?
Swifts Double är en 64-bitars flyttalstyp. Dess intervall rymmer både stora och små storleksordningar, men det betyder inte att den kan bevara varje liten ökning som läggs till ett stort värde. Apple dokumenterar typen och dess konverterings-API:er i Double.
Binär dubbelprecision har 53 signifikanta binära siffror. Därför ökar avståndet mellan representerbara värden med storleken. Goldbergs referens om aritmetik förklarar både denna representation och hur subtraktion av tidigare avrundade operander kan blottlägga information som gått förlorad tidigare. What Every Computer Scientist Should Know About Floating-Point Arithmetic
Tänk på följande uttryckligen illustrativa indata: cellindexet 2^40, en cellbredd på 1024 scenenheter och en markörförskjutning på 2^-30 scenenheter. När celladressen och bredden kombineras blir den absoluta koordinaten 2^50. Vid den positiva koordinaten ligger intilliggande Double-värden 0.25 enheter från varandra. När den lilla förskjutningen läggs till avrundas resultatet tillbaka till den ursprungliga koordinaten.
Om du subtraherar origo efteråt får du därför noll. Om du multiplicerar nollan med en stor zoomfaktor kan förskjutningen inte återskapas. Förlusten inträffade när den absoluta koordinaten sattes samman.
Använd x.ulp för att undersöka avståndet mellan värden vid den storleksordning du faktiskt lagrar. Apple definierar detta som enheten i sista positionen och dokumenterar dess undantag, inklusive icke-finita värden. Double.ulpOfOne beskriver avståndet vid ett; det är inte en universell absolut tolerans för scenkoordinater. ulp
Vad bör koordinatrepresentationen bevara?
För den här canvasen representerar du varje axel med ett Int64-cellindex och en lokal Double-förskjutning. En position betyder konceptuellt cell × cellSize + local, men beräkningen bör undvika att skapa detta absoluta flyttalsvärde.
Behåll markören och visningsområdets origo i denna uppdelade form från början. Att omvandla en redan avrundad absolut koordinat till en cell och en förskjutning kan inte återskapa detaljer som gått förlorade.
Följande är designantaganden för det genomarbetade exemplet, inte universella numeriska gränser:
- Varje cell är exakt
1024scenenheter bred. - Cellindex använder
Int64; de är adresser eller antal, inte flyttalsstorheter. - Varje lokal förskjutning är ändlig och ligger i
0..<1024. - Detaljerad positionering accepterar endast ursprungscellen och dess närmaste grannar.
- Zoom är ett ändligt, positivt antal visningsenheter per scenenhet.
- Skärmorigot är den syntetiska ritkoordinaten
512.
Beräkna den relativa koordinaten som cellDifference × cellSize + localDifference. Det viktiga är att subtrahera cellindexen som heltal innan differensen konverteras till Double. Apples vanliga heltalskonvertering kan avrunda; Double(exactly:) misslyckas i stället när exakt representation inte är möjlig. Double
Även subtraktion av två giltiga Int64-värden kan överskrida typens intervall. Använd subtractingReportingOverflow(_:) och kontrollera dess flagga innan du använder resultatet. Apple dokumenterar uttryckligen att delresultatet trunkeras vid överflöde. subtractingReportingOverflow(_:)
Den här uppdelningen bevarar en stor adress oberoende av lokala detaljer. Precisionen beror fortfarande på de lokala förskjutningarna: en mycket liten ökning kan försvinna när den läggs till en tillräckligt stor lokal förskjutning. Välj cellbredder och lokala ankare utifrån den minsta detalj som den aktuella vyn behöver.
Kör en fullständig lokal beräkning på din Mac
Du behöver en fungerande Swift-verktygskedja. Följ vid behov de aktuella instruktionerna i Install Swift — macOS. Kontrollera installationen i Terminal och skapa ett litet körbart paket med arbetsflödet som dokumenteras i Build a Command-line Tool.
swift --version
mkdir SceneCoordinates
cd SceneCoordinates
swift package init --name SceneCoordinates --type executable
Ersätt dess Swift-fil med startpunkten i den genererade körbara målkatalogen under Sources med hela programmet nedan. Använd en startpunktsfil med det här programmet och inga ytterligare toppnivåsatser. Det importerar endast Swifts standardbibliotek och kräver inga paketberoenden.
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)
}
}
Kör paketet från dess katalog:
swift run SceneCoordinates
De förväntade numeriska resultaten är 0.25, 0.0, 513.0 respektive 513.0. Dessa är härledda resultat för de angivna syntetiska indatavärdena. Det underliggande binary64-avståndet, den kollapsade förskjutningen och skalningen med tvåpotenser kontrollerades med Python i forskningsmiljön; där fanns ingen Swift-kompilator tillgänglig, så Swift-programmet granskades men kördes inte.
Det första stabila resultatet bevarar 2^-30 separat från den enorma celladressen och skalar det sedan med 2^30 till en visningsenhet. Det andra använder 2^-600 med en zoom på 2^600; båda indatavärdena och deras produkt är representerbara i det här exemplet. En liten storleksordning innebär inte i sig att precision har gått förlorad.
Programmet använder a.addingProduct(b, c) för a + b × c. Apple dokumenterar detta som en sammansmält operation utan mellanliggande avrundning. Det minskar avrundningen inom den operationen; den lokala subtraktionen och eventuell tidigare avrundning av indata är fortfarande separata. addingProduct(::)
Hur väljer du en användbar precisionsbudget?
Börja med den minsta separation som måste förbli synlig vid den aktuella zoomen. För ett illustrativt mål på en fjärdedels visningsenhet delar du 0.25 med zoom för att få en felbudget i scenrymden. Undersök sedan avståndet mellan värden vid de lokala koordinater och mellanresultat som beräkningen använder.
Vid zoom 2^30 är den illustrativa budgeten till exempel 2^-32 scenenheter. Vid den lokala koordinaten 512 är Double-avståndet 2^-43, betydligt finare än den budgeten. Vid den absoluta koordinaten 2^50 är avståndet 0.25, vilket är alldeles för grovt. Dessa beräknade värden förklarar varför ett lokalt ankare i närheten hjälper just den här vyn.
Avståndet är ett diagnostiskt mått, inte en fullständig felgräns. Indatakonvertering, subtraktion, transformeringar och den slutliga ritkonverteringen kan alla bidra med fel. Testa kända förskjutningar genom hela kedjan, inklusive den faktiska renderaren, innan du godkänner ett zoomintervall.
| Observation | Troligt numeriskt problem | Nästa åtgärd |
|---|---|---|
| När du lägger till en förskjutning ändras inte den lagrade koordinaten | Lagringsavståndet överstiger ökningen | Bevara förskjutningen separat eller välj ett närmare ankare |
| Relativa koordinater fungerar fram till konverteringen | Målprecisionen är otillräcklig | Konvertera efter subtraktion av origo och zoom; kontrollera det resulterande felet |
| Värden blir oändliga | En operation överskrider det ändliga intervallet | Ändra beräkningsordningen eller begränsa det vyintervall som stöds |
| En förskjutning skild från noll blir noll | Underflöde eller avrundning raderade den | Skala om tidigare eller ändra den lokala representationen |
| Programmet avvisar en avlägsen cell | Policyn för detaljerad vy har överskridits | Välj en närmare origo eller använd en separat grov översiktsberäkning |
I en översiktsvy kan vissa små detaljer med rätta motsvara betydligt mindre än en visningsenhet. Definiera detta beteende uttryckligen. Exemplet begränsar avsiktligt detaljerad aritmetik till celler intill ursprungscellen. Om begränsningen utökas krävs en ny precisionsbudget; det räcker inte att bara ta bort kontrollen.
Vad händer vid mycket små skalor och under ritning?
Apple skiljer leastNormalMagnitude från mindre positiva subnormala värden. Subnormala värden har lägre precision än normala värden, så det minsta positiva talet är inte en bra standardskala att arbeta med. Kontrollera mellanresultaten såväl som indatavärdena när mycket små förskjutningar multipliceras med zoom. leastNormalMagnitude
Exemplet avvisar en förskjutning skild från noll som skalas till noll eller blir omöjlig att skilja från skärmorigot. Det intygar inte att varje resultat skilt från noll ligger inom en visuell tolerans. Lägg till ett toleransbaserat godkännandekriterium som passar din canvas och hantera kastade fel uttryckligen i stället för att ersätta dem med origokoordinaten.
För en tvådimensionell canvas bevarar du en uppdelad position för varje axel och beräknar båda relativa koordinaterna innan du ritar. För en tredimensionell utökning tillhandahåller Apple simd_double3, ett alias för SIMD3<Double>. Varje komponent förblir ett 64-bitars flyttalsvärde med sin egen precisionsgräns. simd_double3
Behåll det uppdelade scenvärdet som den auktoritativa positionen. Konvertera endast de slutliga närliggande ritkoordinaterna till den typ som rit-API:t kräver, och mät konverteringsfelet mot budgeten i visningsrymden. Om dessa slutliga värden fortfarande är enorma bör du först avgöra om objektet hör hemma i det aktuella visningsområdet.
En stabil canvas behöver ett angivet intervall som stöds: heltalsbaserade celladresser, begränsade lokala förskjutningar, ett zoomintervall och ett godtagbart fel i visningsrymden. Bevara detaljer innan du sätter samman stora värden, beräkna relativt en närliggande origo och verifiera representativa förskjutningar vid intervallets gränser.
Källor
- 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