Pour convertir une taille de grille calculée en nombre entier en Swift, choisissez d’abord la règle d’arrondi, puis utilisez Int(exactly:) pour vérifier qu’elle est représentable. Pour une zone de dessin sur Mac couverte de carreaux, arrondissez chaque axe à l’entier supérieur et vérifiez que la multiplication finale ne déborde pas.
Cet article détaille ce calcul pour un outil de dessin de bureau : à partir d’une zone de dessin rectangulaire et de carreaux de taille fixe, déterminer le nombre de colonnes, de lignes et de carreaux nécessaires pour la couvrir. Toutes les coordonnées et tous les nombres ci-dessous sont fictifs. La grille commence à l’origine de la zone de dessin, ne comporte aucun espace et autorise des carreaux partiels le long des bords extérieurs.
Choisir la règle d’arrondi selon l’objectif de la grille
Une dimension de grille fractionnaire nécessite une règle avant toute conversion de type. Si une zone de dessin mesure 650 points de large et que chaque carreau mesure 64 points de large, la dimension calculée est 650.0 / 64.0, soit 10.15625. Pour couvrir toute la largeur, il faut 11 colonnes. Si l’on compte uniquement les carreaux entiers qui tiennent dans cette largeur, on en obtient 10.
Swift fournit des règles d’arrondi explicites pour ces différentes tâches. .up arrondit vers l’infini positif, .down vers l’infini négatif et .towardZero supprime la partie fractionnaire. L’arrondi au plus proche comporte des règles distinctes pour les cas d’égalité. Ces comportements sont définis dans la documentation d’Apple sur FloatingPointRoundingRule.
| Objectif | Règle pour une dimension d’axe non négative | Résultat pour 10.15625 |
|---|---|---|
| Couvrir toute la surface, carreau de bordure compris | .up |
11.0 |
| Compter les carreaux entiers qui tiennent dans la surface | .down |
10.0 |
| Choisir la dimension entière la plus proche | .toNearestOrAwayFromZero |
10.0 |
| Exiger que la dimension calculée soit déjà entière | Aucun arrondi ; essayer directement Int(exactly:) |
nil |
Pour couvrir la surface, utilisez .rounded(.up). Appeler .rounded() sans règle sélectionne l’arrondi au plus proche, avec les cas d’égalité arrondis en s’éloignant de zéro, ce qui peut laisser une partie de cette zone de dessin sans couverture. De plus, l’arrondi d’un Double renvoie un Double dont la valeur est entière ; la conversion en entier reste une étape distincte. Consultez la documentation d’Apple sur rounded(_:).
Arrondir séparément les colonnes et les lignes
Supposons que la zone de dessin fictive s’étende de la coordonnée horizontale 20 à 670 et de la coordonnée verticale 30 à 440. Sa largeur est de 650 points et sa hauteur de 410 points. Avec des carreaux carrés de 64 points de côté, le calcul de couverture est le suivant :
- Colonnes :
ceil(650 / 64) = 11. - Lignes :
ceil(410 / 64) = 7. - Total :
11 × 7 = 77carreaux.
Ces résultats arithmétiques sont illustratifs et découlent de la disposition indiquée. Diviser la surface de la zone de dessin par celle d’un carreau et n’arrondir qu’une fois donne 66, ce qui ne décrit pas l’agencement rectangulaire nécessaire. Chaque ligne nécessite 11 positions de carreaux, et la zone de dessin nécessite sept lignes de ce type. Conservez ces deux dimensions jusqu’à ce qu’elles aient toutes deux été arrondies.
Cette formule suppose que le premier carreau commence à l’origine de la zone de dessin. Si les carreaux appartiennent plutôt à une grille ancrée ailleurs, le décalage initial peut modifier le nombre de cellules intersectées par le rectangle. Déterminez cet alignement avant d’appliquer un calcul fondé uniquement sur la largeur.
Pourquoi une conversion directe en entier ne suffit pas
L’initialiseur ordinaire de Swift pour convertir un flottant en entier arrondit vers zéro. Ainsi, Int(10.15625) produit 10. La valeur source doit également tenir dans le type entier de destination après troncature ; Apple indique qu’une conversion hors plage peut provoquer une erreur à l’exécution. Consultez BinaryInteger.init(_:).
Pour une dimension de couverture calculée, la séquence utile est donc Int(exactly: value.rounded(.up)), suivie d’une gestion facultative du résultat. Apple précise que Int.init(exactly:) renvoie nil lorsque la valeur flottante fournie ne peut pas être représentée exactement sous forme d’entier.
Cet initialiseur répond à une question précise : cette valeur fournie tient-elle exactement dans le type ? Il ne permet pas d’établir que la géométrie d’origine est valide, que la règle d’arrondi choisie convient à la disposition ou qu’il est réaliste de créer autant de carreaux. Ce sont des vérifications distinctes, effectuées dans l’implémentation ci-dessous.
Programme Swift complet pour limiter le nombre de carreaux
Le programme suivant utilise Double pour les coordonnées et dimensions, et Int pour les décomptes. Il exige des dimensions finies et non négatives, des dimensions de carreaux finies et strictement positives, ainsi qu’une limite de carreaux positive définie par l’application. Une zone de dessin dont l’une des dimensions est égale à zéro produit une grille vide. Les bornes inversées sont rejetées plutôt que réordonnées silencieusement.
Enregistrez ce programme complet sous le nom GridCount.swift et exécutez-le avec swift GridCount.swift sur un Mac disposant d’une chaîne d’outils Swift. Il n’utilise que la bibliothèque standard de Swift ; aucun import n’est donc nécessaire. Le programme est présenté avec les résultats attendus ; il a été examiné, mais n’a pas été exécuté avec un compilateur Swift au cours de cette recherche.
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)")
}
Rôle des vérifications
Les vérifications de valeurs finies rejettent NaN et l’infini avant qu’ils ne puissent devenir des nombres de carreaux. Apple précise que isFinite vaut false pour ces deux valeurs. Exiger des dimensions de carreaux strictement positives exclut également la division par zéro et les tailles de carreaux négatives.
Il est important de vérifier séparément le quotient, même après validation des entrées. L’implémentation rejette un quotient non fini, ainsi que la valeur zéro lorsque la dimension est positive. Cette dernière vérification fait échouer explicitement un quotient positif extrêmement faible qui passe à zéro par sous-dépassement, au lieu de produire un axe vide. C’est une limite délibérée de l’exemple.
Même après la conversion réussie de chaque axe, le total doit encore être vérifié. Le produit de deux valeurs Int valides peut dépasser la plage de ce type. La documentation d’Apple sur multipliedReportingOverflow(by:) précise que partialValue contient le produit complet uniquement lorsque overflow vaut false. Le programme vérifie ce drapeau avant d’utiliser le produit.
La comparaison finale maxCells a un autre rôle : elle applique une limite définie par l’application, même si le calcul tient dans la plage. La valeur illustrative de 10 000 relève d’une règle choisie par l’appelant ; elle ne constitue pas une recommandation en matière de mémoire ou de performances de dessin. Choisissez une limite en fonction de ce que votre application crée réellement pour chaque carreau.
Éviter une vérification de plage flottante à la limite des entiers
Une vérification tentante consiste à utiliser value <= Double(Int.max), puis Int(value). Cette comparaison peut induire en erreur, car la conversion de la limite entière en flottant peut l’arrondir. Apple indique explicitement que Double.init(_:) à partir d’un Int effectue une conversion vers la représentation la plus proche possible.
Sur une cible 64 bits, Int.max vaut 9_223_372_036_854_775_807. Sa conversion en Double est arrondie à 9_223_372_036_854_775_808, soit une unité de plus. Une valeur flottante égale à cette limite arrondie passerait la comparaison tout en restant hors de la plage des entiers. Apple documente le rapport entre la largeur de la plateforme et la taille des entiers sur sa page de référence Int.
Cet exemple de limite est une illustration arithmétique, pas une taille de zone de dessin proposée. Il explique pourquoi le programme demande à Int(exactly:) d’effectuer directement la vérification de représentabilité. Cela évite également de coder en dur un nombre maximal dépendant d’une largeur entière particulière.
Déterminer le comportement des valeurs proches d’une limite de carreau
L’arrondi vers le haut suit la valeur effectivement stockée. Un quotient légèrement supérieur à deux nécessite trois carreaux selon cette règle de couverture. La propriété nextUp de Swift fournit la valeur flottante représentable suivante, supérieure à la valeur courante ; ainsi, (2.0).nextUp.rounded(.up) vaut 3.0. La signification de cette valeur adjacente est documentée dans la référence nextUp d’Apple.
Ne soustrayez pas un epsilon arbitraire uniquement pour donner à une valeur affichée l’apparence d’un entier. Cela modifie la règle de couverture et peut supprimer un carreau nécessaire pour couvrir un petit reste réel. Si l’application aligne intentionnellement les coordonnées sur une grille, définissez cette règle d’alignement en amont et calculez les nombres à partir de la géométrie ainsi alignée. Si les dimensions proviennent d’unités entières exactes, envisagez de conserver ces unités tout au long du calcul du nombre de carreaux.
L’exemple suppose également que la soustraction des coordonnées fournies donne des dimensions utiles. Une conversion finale exacte ne peut pas récupérer les détails déjà perdus lors des calculs de coordonnées précédents. Choisissez des ordres de grandeur de coordonnées adaptés à la disposition et utilisez les mêmes unités pour la zone de dessin et les dimensions des carreaux.
Vérifier les cas qui distinguent les comportements corrects
Utilisez des cas limites pour vérifier votre adaptation, en particulier si vous modifiez la règle d’arrondi ou le comportement d’une grille vide. Les résultats attendus suivants découlent du contrat indiqué par ce programme ; ils ne correspondent pas à des tests Swift exécutés.
| Entrée fictive | Résultat attendu |
|---|---|
Zone de dessin 650 × 410, carreaux 64 × 64, limite 10_000 |
11 colonnes, 7 lignes, 77 carreaux |
Zone de dessin 128 × 128, carreaux 64 × 64, limite 10_000 |
2 colonnes, 2 lignes, 4 carreaux |
Zone de dessin 129 × 128, carreaux 64 × 64, limite 10_000 |
3 colonnes, 2 lignes, 6 carreaux |
| L’une des dimensions de la zone de dessin est nulle ; les autres entrées sont valides | Grille vide, total égal à zéro |
| Dimension négative, dimension de carreau nulle ou entrée non finie | invalidInput |
| Une dimension positive divisée par la taille d’un carreau donne zéro ou l’infini | unrepresentableAxis |
Un axe arrondi vers le haut ne tient pas dans un Int |
unrepresentableAxis |
| Des nombres valides par axe dont le produit déborde | countOverflow |
L’exemple des 77 carreaux avec une limite de 50 |
exceedsLimit |
Pour cette zone de dessin sur Mac, conservez la géométrie en virgule flottante jusqu’au calcul de chaque axe, arrondissez vers le haut pour inclure les carreaux de bordure partiels et convertissez à l’aide de Int(exactly:). Vérifiez ensuite la multiplication et la limite de l’application avant de créer la grille. Cette séquence convertit les dimensions calculées en nombres entiers avec un comportement d’échec explicite et vérifiable.
Sources
- FloatingPointRoundingRule — Apple
- rounded(_:) — Apple
- BinaryInteger.init(_:) — Apple
- Int.init(exactly:) — Apple
- isFinite — Apple
- multipliedReportingOverflow(by:) — Apple
- Double.init(_:) — Apple
- Int — Apple
- nextUp — Apple