Per trasformare una dimensione di griglia calcolata in un conteggio intero in Swift, scegli prima la regola di arrotondamento, quindi usa Int(exactly:) per verificare che il valore sia rappresentabile. Per un canvas Mac coperto da tessere, arrotonda per eccesso ciascun asse e controlla che la moltiplicazione finale non vada in overflow.
Questo articolo sviluppa il calcolo per uno strumento di disegno desktop: dato un canvas rettangolare e tessere di dimensioni fisse, determinare le colonne, le righe e il numero totale di tessere necessarie per coprirlo. Tutte le coordinate e i conteggi riportati di seguito sono sintetici. La griglia inizia dall’origine del canvas, non prevede spazi e ammette tessere parziali lungo i bordi esterni.
Scegliere la regola di arrotondamento in base allo scopo della griglia
Una dimensione di griglia frazionaria richiede una regola prima di una conversione di tipo. Se un canvas è largo 650 punti e ogni tessera è larga 64 punti, la dimensione calcolata è 650.0 / 64.0, ovvero 10.15625. Per coprire l’intera larghezza servono 11 colonne. Se invece si contano solo le tessere intere che rientrano in quella larghezza, ne servono 10.
Swift offre regole di arrotondamento esplicite per queste diverse operazioni. .up arrotonda verso l’infinito positivo, .down verso l’infinito negativo e .towardZero elimina la parte frazionaria. L’arrotondamento al valore più vicino prevede regole separate per i casi di parità. Questi comportamenti sono definiti nella documentazione di Apple su FloatingPointRoundingRule.
| Operazione | Regola per la dimensione di un asse non negativa | Risultato per 10.15625 |
|---|---|---|
| Coprire l’intera estensione, includendo una tessera sul bordo | .up |
11.0 |
| Contare le tessere intere che rientrano nell’estensione | .down |
10.0 |
| Scegliere la dimensione intera più vicina | .toNearestOrAwayFromZero |
10.0 |
| Richiedere che la dimensione calcolata sia già intera | Nessun arrotondamento; provare direttamente Int(exactly:) |
nil |
Per l’operazione di copertura, usa .rounded(.up). Chiamare .rounded() senza specificare una regola seleziona l’arrotondamento al valore più vicino, con i casi di parità arrotondati lontano da zero: una scelta che può lasciare scoperta una parte di questo canvas. Inoltre, arrotondare un Double restituisce un Double con valore intero; la conversione a intero è un passaggio distinto. Consulta la documentazione di Apple su rounded(_:).
Arrotondare colonne e righe separatamente
Supponiamo che il canvas sintetico si estenda dalla coordinata orizzontale 20 alla 670 e da quella verticale 30 alla 440. La larghezza è di 650 punti e l’altezza di 410 punti. Con tessere quadrate di 64 punti per lato, il calcolo della copertura è il seguente:
- Colonne:
ceil(650 / 64) = 11. - Righe:
ceil(410 / 64) = 7. - Totale:
11 × 7 = 77tessere.
Sono risultati aritmetici illustrativi, ricavati dalla disposizione specificata. Dividere l’area del canvas per l’area di una tessera e arrotondare una sola volta dà 66, un valore che non descrive la disposizione rettangolare necessaria. Ogni riga richiede 11 posizioni per le tessere e il canvas richiede sette righe di questo tipo. Mantieni separate queste due dimensioni finché non le hai arrotondate entrambe.
Questa formula presuppone che la prima tessera inizi dall’origine del canvas. Se invece le tessere appartengono a una griglia ancorata altrove, l’offset iniziale può cambiare il numero di celle intersecate dal rettangolo. Definisci l’allineamento prima di applicare un calcolo basato soltanto sulla larghezza.
Perché una conversione diretta a intero non basta
Il normale inizializzatore da virgola mobile a intero di Swift arrotonda verso zero. Quindi Int(10.15625) restituisce 10. Inoltre, dopo il troncamento, il valore sorgente deve rientrare nell’intervallo del tipo intero di destinazione; Apple documenta che una conversione fuori intervallo può causare un errore di runtime. Consulta BinaryInteger.init(_:).
Per una dimensione di copertura calcolata, la sequenza utile è quindi Int(exactly: value.rounded(.up)), seguita dalla gestione facoltativa del risultato. Apple documenta che Int.init(exactly:) restituisce nil se il valore in virgola mobile fornito non può essere rappresentato esattamente come intero.
L’inizializzatore risponde a una domanda circoscritta: il valore fornito rientra esattamente nell’intervallo? Non stabilisce che la geometria originale sia valida, che la regola di arrotondamento scelta sia adatta alla disposizione o che sia pratico creare tante tessere. L’implementazione seguente esegue questi controlli separatamente.
Un programma Swift completo per un conteggio di tessere limitato
Il programma seguente usa Double per coordinate e dimensioni e Int per i conteggi. Richiede estensioni finite e non negative, dimensioni delle tessere finite e positive e un limite positivo, definito dall’applicazione, per il numero di tessere. Se una delle due estensioni del canvas è zero, la griglia risultante è vuota. I limiti invertiti vengono rifiutati anziché riordinati silenziosamente.
Salva il programma completo come GridCount.swift ed eseguilo con swift GridCount.swift su un Mac con una toolchain Swift disponibile. Usa solo la libreria standard di Swift, quindi non richiede import. Il programma è presentato con i risultati attesi; è stato esaminato, ma durante questa ricerca non è stato eseguito con un compilatore Swift.
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)")
}
Cosa garantiscono i controlli
I controlli sui valori finiti rifiutano NaN e infinito prima che possano diventare conteggi. Apple specifica che isFinite restituisce false per entrambi. Richiedere dimensioni positive per le tessere esclude inoltre la divisione per zero e le dimensioni negative.
È importante controllare separatamente il quoziente anche dopo aver convalidato gli input. L’implementazione rifiuta un quoziente non finito e rifiuta il valore zero quando l’estensione è positiva. Quest’ultimo controllo rende esplicito il fallimento di un quoziente positivo estremamente piccolo che, per underflow, diventa zero, invece di produrre un asse vuoto. È un limite intenzionale dell’esempio.
Dopo aver convertito correttamente ciascun asse, resta da controllare il totale. Il prodotto di due valori Int validi può non rientrare nell’intervallo del tipo. La documentazione di Apple su multipliedReportingOverflow(by:) specifica che partialValue contiene il prodotto completo solo se overflow è false. Il programma controlla quel flag prima di usare il prodotto.
Il confronto finale maxCells serve a uno scopo diverso: applica un limite dell’applicazione anche quando il calcolo non va in overflow. Il valore illustrativo di 10.000 è una scelta del chiamante, non una raccomandazione relativa alla memoria o alle prestazioni di disegno. Scegli un limite in base a ciò che l’applicazione crea davvero per ogni tessera.
Evitare un confronto in virgola mobile ai limiti dell’intervallo intero
Un controllo allettante consiste nell’usare value <= Double(Int.max) seguito da Int(value). Il confronto può trarre in inganno, perché convertendo in virgola mobile il limite intero, il valore potrebbe essere arrotondato. Apple descrive esplicitamente Double.init(_:) da Int come una conversione alla rappresentazione possibile più vicina.
Su una piattaforma a 64 bit, Int.max è 9_223_372_036_854_775_807. La sua conversione a Double viene arrotondata a 9_223_372_036_854_775_808, un valore maggiore di uno. Un valore in virgola mobile uguale a questo limite arrotondato supererebbe il confronto pur restando fuori dall’intervallo degli interi. Apple documenta la relazione tra la larghezza della piattaforma e la dimensione degli interi nella pagina di riferimento di Int.
Questo esempio al limite è un’illustrazione aritmetica, non una dimensione di canvas proposta. Spiega perché il programma chiede a Int(exactly:) di eseguire direttamente il controllo di rappresentabilità. Evita inoltre di codificare un conteggio massimo legato a una specifica larghezza intera.
Decidere come gestire i valori vicini al bordo di una tessera
L’arrotondamento per eccesso segue il valore effettivamente memorizzato. Un quoziente appena superiore a due richiede tre tessere secondo questa regola di copertura. La proprietà nextUp di Swift fornisce il successivo valore rappresentabile in virgola mobile più grande, quindi (2.0).nextUp.rounded(.up) è 3.0. Il significato di questo valore adiacente è documentato nella documentazione di Apple su nextUp.
Non sottrarre un epsilon arbitrario solo per far apparire intero un valore visualizzato. In questo modo cambi la regola di copertura e potresti eliminare una tessera necessaria per un piccolo resto reale. Se l’applicazione aggancia intenzionalmente le coordinate a una griglia, definisci a monte tale regola e calcola i conteggi sulla geometria così ottenuta. Se le dimensioni provengono da unità intere esatte, valuta la possibilità di mantenere queste unità per tutto il calcolo dei conteggi.
L’esempio presuppone inoltre che la sottrazione delle coordinate fornite produca estensioni utili. Una conversione finale esatta non può recuperare dettagli già persi nei calcoli precedenti delle coordinate. Mantieni le magnitudini delle coordinate adeguate alla disposizione e usa le stesse unità per le dimensioni del canvas e delle tessere.
Verificare i casi che distinguono il comportamento corretto
Usa casi limite per verificare il tuo adattamento, soprattutto se modifichi la regola di arrotondamento o il comportamento della griglia vuota. I risultati attesi seguenti derivano dal contratto dichiarato dal programma; non sono il resoconto di test eseguiti in Swift.
| Input sintetico | Risultato atteso |
|---|---|
Canvas 650 × 410, tessere 64 × 64, limite 10_000 |
11 colonne, 7 righe, 77 tessere |
Canvas 128 × 128, tessere 64 × 64, limite 10_000 |
2 colonne, 2 righe, 4 tessere |
Canvas 129 × 128, tessere 64 × 64, limite 10_000 |
3 colonne, 2 righe, 6 tessere |
| Una delle estensioni del canvas è zero; gli altri input sono validi | Griglia vuota con totale zero |
| Estensione negativa, dimensione della tessera zero o input non finito | invalidInput |
| Un’estensione positiva divisa per una tessera produce zero o infinito | unrepresentableAxis |
Un asse arrotondato per eccesso non rientra in Int |
unrepresentableAxis |
| Il prodotto di conteggi validi per asse va in overflow | countOverflow |
L’esempio delle 77 tessere con limite 50 |
exceedsLimit |
Per questo canvas Mac, mantieni la geometria in virgola mobile finché non hai calcolato ciascun asse, arrotonda per eccesso per includere le tessere parziali sul bordo e converti usando Int(exactly:). Quindi convalida il prodotto e il limite dell’applicazione prima di creare la griglia. Questa sequenza trasforma le dimensioni calcolate in un conteggio con una gestione esplicita e verificabile degli errori.
Fonti
- FloatingPointRoundingRule — Apple
- rounded(_:) — Apple
- BinaryInteger.init(_:) — Apple
- Int.init(exactly:) — Apple
- isFinite — Apple
- multipliedReportingOverflow(by:) — Apple
- Double.init(_:) — Apple
- Int — Apple
- nextUp — Apple