Om een berekende rastergrootte in Swift om te zetten naar een aantal gehele getallen, kies je eerst de afrondingsregel en gebruik je daarna Int(exactly:) om te controleren of de waarde kan worden weergegeven. Rond voor een Mac-canvas dat met tegels wordt bedekt elke as naar boven af en controleer of de uiteindelijke vermenigvuldiging overloopt.
Dit artikel werkt die berekening uit voor een desktoptekenprogramma: bepaal bij een rechthoekig canvas en tegels met een vaste grootte hoeveel kolommen, rijen en tegels nodig zijn om het te bedekken. Alle coördinaten en aantallen hieronder zijn fictief. Het raster begint bij de oorsprong van het canvas zelf, heeft geen tussenruimtes en laat gedeeltelijke tegels langs de buitenranden toe.
Kies de afrondingsregel op basis van het doel van het raster
Voor een fractionele rasterafmeting heb je eerst een beleid nodig en daarna pas een typeconversie. Als een canvas 650 punten breed is en elke tegel 64 punten breed, is de berekende afmeting 650.0 / 64.0, oftewel 10.15625. Om de volledige breedte te bedekken zijn 11 kolommen nodig. Als je alleen de volledige tegels telt die binnen die breedte passen, zijn dat er 10.
Swift biedt expliciete afrondingsregels voor deze verschillende taken. .up rondt af richting positieve oneindigheid, .down richting negatieve oneindigheid en .towardZero verwijdert het fractionele deel. Voor afronden op het dichtstbijzijnde getal gelden afzonderlijke regels voor gelijke afstanden. Deze gedragingen zijn vastgelegd in Apples documentatie over FloatingPointRoundingRule.
| Taak | Regel voor een niet-negatieve asafmeting | Resultaat voor 10.15625 |
|---|---|---|
| Het volledige bereik bedekken, inclusief een randtegel | .up |
11.0 |
| Volledige tegels tellen die binnen het bereik passen | .down |
10.0 |
| De dichtstbijzijnde gehele afmeting kiezen | .toNearestOrAwayFromZero |
10.0 |
| Vereisen dat de berekende afmeting al een geheel getal is | Niet afronden; probeer direct Int(exactly:) |
nil |
Gebruik voor het bedekken .rounded(.up). Als je .rounded() zonder regel aanroept, wordt er standaard afgerond op het dichtstbijzijnde getal, waarbij gelijke afstanden van nul af worden afgerond. Daardoor kan een deel van dit canvas onbedekt blijven. Ook levert het afronden van een Double een Double met een gehele waarde op; conversie naar een geheel getal is een aparte stap. Zie Apples documentatie over rounded(_:).
Rond kolommen en rijen afzonderlijk af
Stel dat het fictieve canvas loopt van horizontale coördinaat 20 tot 670 en van verticale coördinaat 30 tot 440. Het is 650 punten breed en 410 punten hoog. Met vierkante tegels van 64 punten per zijde ziet de berekening voor het bedekken er als volgt uit:
- Kolommen:
ceil(650 / 64) = 11. - Rijen:
ceil(410 / 64) = 7. - Totaal:
11 × 7 = 77tegels.
Dit zijn illustratieve rekenresultaten op basis van de opgegeven indeling. Als je de oppervlakte van het canvas door de oppervlakte van een tegel deelt en het resultaat één keer afrondt, krijg je 66. Dat beschrijft niet de benodigde rechthoekige indeling. Elke rij heeft 11 tegelposities nodig en het canvas heeft zeven van zulke rijen nodig. Houd die twee afmetingen apart totdat ze allebei zijn afgerond.
Deze formule gaat ervan uit dat de eerste tegel bij de oorsprong van het canvas begint. Als de tegels deel uitmaken van een raster met een ander ankerpunt, kan de beginverschuiving veranderen hoeveel cellen de rechthoek snijdt. Bepaal die uitlijning voordat je een berekening gebruikt die alleen van de breedte uitgaat.
Waarom een directe conversie naar een geheel getal niet volstaat
De gewone initializer voor een conversie van een drijvendekommagetal naar een geheel getal in Swift rondt af richting nul. Int(10.15625) levert dus 10 op. Ook moet de bronwaarde na afkappen binnen het bereik van het doeltype vallen; Apple documenteert dat een conversie buiten dat bereik een runtimefout kan veroorzaken. Zie BinaryInteger.init(_:).
Voor een berekende afmeting die het canvas moet bedekken, is de nuttige volgorde daarom Int(exactly: value.rounded(.up)), gevolgd door optionele verwerking. Apple documenteert dat Int.init(exactly:) nil retourneert als de opgegeven drijvendekommawaarde niet exact als geheel getal kan worden weergegeven.
De initializer beantwoordt een beperkte vraag: past deze opgegeven waarde er exact in? Hij stelt niet vast of de oorspronkelijke geometrie geldig was, of de gekozen afrondingsregel bij de indeling past, of dat het praktisch is om zoveel tegels te maken. Dat zijn afzonderlijke controles in de implementatie hieronder.
Een volledig Swift-programma voor een begrensd aantal tegels
Het volgende programma gebruikt Double voor coördinaten en afmetingen en Int voor aantallen. Het vereist eindige, niet-negatieve afstanden; eindige, positieve tegelafmetingen; en een positieve, door de applicatie bepaalde limiet voor het aantal tegels. Een canvas waarvan een van beide afstanden nul is, levert een leeg raster op. Omgekeerde grenzen worden afgewezen in plaats van stilzwijgend omgedraaid.
Sla dit volledige programma op als GridCount.swift en voer het uit met swift GridCount.swift op een Mac waarop een Swift-toolchain beschikbaar is. Het gebruikt alleen de standaardbibliotheek van Swift, dus imports zijn niet nodig. Het programma wordt met verwachte resultaten gepresenteerd; het is nagekeken, maar tijdens dit onderzoek niet met een Swift-compiler uitgevoerd.
enum GridError: Error {
case invalidInput
case unrepresentableAxis
case countOverflow
case exceedsLimit
}
struct GridCount {
let columns: Int
let rows: Int
let total: Int
}
func coveringAxis(extent: Double, tile: Double) throws -> Int {
guard extent.isFinite, extent >= 0,
tile.isFinite, tile > 0 else {
throw GridError.invalidInput
}
if extent == 0 { return 0 }
let quotient = extent / tile
guard quotient.isFinite, quotient > 0,
let count = Int(exactly: quotient.rounded(.up)),
count > 0 else {
throw GridError.unrepresentableAxis
}
return count
}
func coveringGrid(
width: Double,
height: Double,
tileWidth: Double,
tileHeight: Double,
maxCells: Int
) throws -> GridCount {
guard width.isFinite, width >= 0,
height.isFinite, height >= 0,
tileWidth.isFinite, tileWidth > 0,
tileHeight.isFinite, tileHeight > 0,
maxCells > 0 else {
throw GridError.invalidInput
}
if width == 0 || height == 0 {
return GridCount(columns: 0, rows: 0, total: 0)
}
let columns = try coveringAxis(extent: width, tile: tileWidth)
let rows = try coveringAxis(extent: height, tile: tileHeight)
let product = columns.multipliedReportingOverflow(by: rows)
guard !product.overflow else {
throw GridError.countOverflow
}
guard product.partialValue <= maxCells else {
throw GridError.exceedsLimit
}
return GridCount(
columns: columns,
rows: rows,
total: product.partialValue
)
}
let left: Double = 20
let right: Double = 670
let top: Double = 30
let bottom: Double = 440
do {
let grid = try coveringGrid(
width: right - left,
height: bottom - top,
tileWidth: 64,
tileHeight: 64,
maxCells: 10_000
)
print("columns=\(grid.columns), rows=\(grid.rows), total=\(grid.total)")
// Expected: columns=11, rows=7, total=77
} catch {
print("Grid calculation failed: \(error)")
}
Wat de controles doen
De controles op eindige waarden wijzen NaN en oneindigheid af voordat een van beide in een aantal kan worden omgezet. Apple geeft aan dat isFinite voor beide false is. Door positieve tegelafmetingen te vereisen, worden ook deling door nul en negatieve tegelafmetingen uitgesloten.
Het is belangrijk om het quotiënt afzonderlijk te controleren, ook nadat de invoer is gevalideerd. De implementatie wijst een niet-eindig quotiënt af en wijst ook nul af wanneer de afstand positief was. Die laatste controle maakt van een extreem klein positief quotiënt dat door underflow nul wordt, een expliciete fout in plaats van een lege as. Dit is een bewuste grens van het voorbeeld.
Ook na een geslaagde conversie van elke as moet het totaal nog worden gecontroleerd. Twee geldige Int-waarden kunnen samen een product opleveren dat buiten het bereik van het type valt. Apples documentatie over multipliedReportingOverflow(by:) geeft aan dat partialValue het volledige product bevat wanneer overflow false is. Het programma controleert die vlag voordat het product wordt gebruikt.
De laatste vergelijking met maxCells dient een ander doel: ze dwingt een applicatielimiet af, ook als de berekening binnen het bereik valt. De illustratieve waarde van 10.000 is een door de aanroeper gekozen beleidskeuze, geen aanbeveling voor geheugen- of tekenprestaties. Kies een limiet op basis van wat je applicatie daadwerkelijk voor elke tegel aanmaakt.
Vermijd een controle van de drijvendekommagrenzen bij de grens van het gehele-getalbereik
Een verleidelijke controle is value <= Double(Int.max), gevolgd door Int(value). Die vergelijking kan misleidend zijn, omdat de omzetting van de grens van het gehele-getalbereik naar een drijvendekommagetal afronding kan veroorzaken. Apple omschrijft Double.init(_:) vanuit Int expliciet als een conversie naar de dichtstbijzijnde mogelijke representatie.
Op een 64-bitsdoel is Int.max gelijk aan 9_223_372_036_854_775_807. De omzetting naar Double wordt afgerond op 9_223_372_036_854_775_808, één groter. Een drijvendekommawaarde die gelijk is aan die afgeronde grens zou de vergelijking doorstaan, terwijl die buiten het bereik van het gehele getal valt. Apple documenteert het verband tussen platformbreedte en de grootte van gehele getallen op de referentiepagina voor Int.
Dit grensgeval is een rekenkundige illustratie, geen voorgestelde canvasgrootte. Het verklaart waarom het programma Int(exactly:) rechtstreeks om de controle op representatie vraagt. Zo hoeft ook geen maximumaantal hard gecodeerd te worden dat afhankelijk is van een bepaalde breedte van het gehele-getaltype.
Bepaal hoe je waarden vlak bij een tegelgrens behandelt
Naar boven afronden volgt de waarde die daadwerkelijk is opgeslagen. Een quotiënt dat net boven twee ligt, vereist volgens dit beleid om het gebied te bedekken drie tegels. De eigenschap nextUp van Swift levert de eerstvolgende grotere, representeerbare drijvendekommawaarde op, dus (2.0).nextUp.rounded(.up) is 3.0. De betekenis van die aangrenzende waarde staat beschreven in Apples referentie over nextUp.
Trek niet zomaar een willekeurige epsilon af om een weergegeven waarde er geheel uit te laten zien. Daarmee verander je het beleid voor het bedekken en kan een tegel verdwijnen die nodig is voor een kleine werkelijke rest. Als de applicatie coördinaten bewust op een raster vastzet, definieer die afrondingsregel dan stroomopwaarts en bereken de aantallen op basis van de aangepaste geometrie. Als afmetingen afkomstig zijn uit exacte gehele eenheden, overweeg dan om die eenheden tijdens de berekening van de aantallen te behouden.
Het voorbeeld gaat er ook van uit dat het aftrekken van de opgegeven coördinaten bruikbare afstanden oplevert. Een exacte conversie aan het einde kan geen details herstellen die eerder in de coördinaatberekening verloren zijn gegaan. Houd de coördinaatgroottes passend bij de indeling en gebruik dezelfde eenheden voor het canvas en de tegelafmetingen.
Controleer de gevallen die correct gedrag onderscheiden
Gebruik grensgevallen om je aanpassing te controleren, vooral als je het afrondingsbeleid of het gedrag voor lege rasters wijzigt. De volgende verwachte resultaten volgen uit het beschreven contract van dit programma; het zijn geen gemelde uitvoeringen van Swift-tests.
| Fictieve invoer | Verwacht resultaat |
|---|---|
Canvas 650 × 410, tegels 64 × 64, limiet 10_000 |
11 kolommen, 7 rijen, 77 tegels |
Canvas 128 × 128, tegels 64 × 64, limiet 10_000 |
2 kolommen, 2 rijen, 4 tegels |
Canvas 129 × 128, tegels 64 × 64, limiet 10_000 |
3 kolommen, 2 rijen, 6 tegels |
| Een van beide canvasafstanden is nul; overige invoer is geldig | Leeg raster met totaal nul |
| Negatieve afstand, tegelafmeting nul of niet-eindige invoer | invalidInput |
| Positieve afstand gedeeld door een tegel levert nul of oneindigheid op | unrepresentableAxis |
Een naar boven afgeronde as past niet in Int |
unrepresentableAxis |
| Geldige aantallen per as waarvan het product overloopt | countOverflow |
Het voorbeeld met 77 tegels en limiet 50 |
exceedsLimit |
Houd voor dit Mac-canvas de geometrie in drijvendekommagetallen totdat elke as is berekend, rond naar boven af om gedeeltelijke randtegels mee te nemen en converteer met Int(exactly:). Controleer vervolgens de vermenigvuldiging en de applicatielimiet voordat je het raster aanmaakt. Zo worden de berekende afmetingen omgezet in aantallen met expliciet en controleerbaar foutgedrag.
Bronnen
- FloatingPointRoundingRule — Apple
- rounded(_:) — Apple
- BinaryInteger.init(_:) — Apple
- Int.init(exactly:) — Apple
- isFinite — Apple
- multipliedReportingOverflow(by:) — Apple
- Double.init(_:) — Apple
- Int — Apple
- nextUp — Apple