Para convertir un tamaño de cuadrícula calculado en un recuento entero en Swift, elige primero la regla de redondeo y, después, usa Int(exactly:) para comprobar que el valor se puede representar. Para cubrir un lienzo de Mac con mosaicos, redondea cada eje hacia arriba y comprueba que la multiplicación final no produzca desbordamiento.
Este artículo desarrolla ese cálculo para una herramienta de dibujo de escritorio: dado un lienzo rectangular y mosaicos de tamaño fijo, determina las columnas, las filas y el total de mosaicos necesarios para cubrirlo. Todas las coordenadas y los recuentos que aparecen a continuación son ficticios. La cuadrícula empieza en el origen del propio lienzo, no tiene espacios y permite mosaicos parciales en los bordes exteriores.
Elige la regla de redondeo según el propósito de la cuadrícula
Una dimensión fraccionaria de la cuadrícula necesita una política antes que una conversión de tipo. Si un lienzo tiene 650 puntos de ancho y cada mosaico mide 64 puntos de ancho, la dimensión calculada es 650.0 / 64.0, o 10.15625. Para cubrir todo el ancho se necesitan 11 columnas. Si solo se cuentan los mosaicos completos que caben en ese ancho, son 10.
Swift ofrece reglas de redondeo explícitas para estas distintas tareas. .up redondea hacia el infinito positivo, .down hacia el infinito negativo y .towardZero elimina la parte fraccionaria. El redondeo al entero más cercano tiene reglas distintas para los empates. Estos comportamientos se definen en la documentación de Apple sobre FloatingPointRoundingRule.
| Tarea | Regla para una dimensión de eje no negativa | Resultado para 10.15625 |
|---|---|---|
| Cubrir toda la extensión, incluido un mosaico en el borde | .up |
11.0 |
| Contar los mosaicos completos que caben en la extensión | .down |
10.0 |
| Elegir la dimensión entera más cercana | .toNearestOrAwayFromZero |
10.0 |
| Exigir que la dimensión calculada ya sea un entero | Sin redondeo; probar directamente con Int(exactly:) |
nil |
Para cubrir toda la extensión, usa .rounded(.up). Si se llama a .rounded() sin especificar una regla, se aplica el redondeo al entero más cercano y los empates se alejan de cero, lo que puede dejar parte de este lienzo sin cubrir. Además, redondear un Double devuelve un Double con valor entero; la conversión a entero es un paso aparte. Consulta la documentación de Apple sobre rounded(_:).
Redondea las columnas y las filas por separado
Supongamos que el lienzo se extiende desde la coordenada horizontal 20 hasta la 670 y desde la coordenada vertical 30 hasta la 440. Tiene 650 puntos de ancho y 410 puntos de alto. Con mosaicos cuadrados de 64 puntos por lado, el cálculo de cobertura es el siguiente:
- Columnas:
ceil(650 / 64) = 11. - Filas:
ceil(410 / 64) = 7. - Total:
11 × 7 = 77mosaicos.
Son resultados aritméticos ilustrativos, derivados de la distribución indicada. Dividir el área del lienzo por el área de los mosaicos y redondear una sola vez da 66, una cifra que no describe la disposición rectangular necesaria. Cada fila necesita 11 posiciones para mosaicos y el lienzo necesita siete filas de este tipo. Conserva ambas dimensiones hasta haberlas redondeado.
Esta fórmula supone que el primer mosaico empieza en el origen del lienzo. Si los mosaicos pertenecen a una cuadrícula anclada en otro lugar, el desplazamiento inicial puede cambiar cuántas celdas intersecta el rectángulo. Determina esa alineación antes de aplicar un cálculo basado solo en el ancho.
Por qué no basta con una conversión directa a entero
El inicializador habitual de Swift para convertir de coma flotante a entero redondea hacia cero. Por tanto, Int(10.15625) produce 10. Además, el valor de origen debe caber en el tipo entero de destino después del truncamiento; Apple documenta que una conversión fuera de rango puede causar un error en tiempo de ejecución. Consulta BinaryInteger.init(_:).
Por tanto, para una dimensión de cobertura calculada, la secuencia adecuada es Int(exactly: value.rounded(.up)), seguida de un tratamiento opcional. Apple documenta que Int.init(exactly:) devuelve nil cuando el valor de coma flotante proporcionado no se puede representar exactamente como un entero.
El inicializador responde a una pregunta concreta: ¿este valor dado cabe exactamente? No determina si la geometría original era válida, si la regla de redondeo elegida es adecuada para la distribución ni si resulta práctico crear esa cantidad de mosaicos. La implementación siguiente realiza esas comprobaciones por separado.
Un programa Swift completo para un recuento limitado de mosaicos
El siguiente programa usa Double para las coordenadas y dimensiones e Int para los recuentos. Exige extensiones finitas y no negativas, dimensiones de mosaico finitas y positivas, y un límite positivo de mosaicos definido por la aplicación. Si cualquiera de las extensiones del lienzo es igual a cero, se obtiene una cuadrícula vacía. Se rechazan los límites invertidos en lugar de reordenarlos sin avisar.
Guarda este programa completo como GridCount.swift y ejecútalo con swift GridCount.swift en un Mac que tenga un conjunto de herramientas de Swift disponible. Solo usa la biblioteca estándar de Swift, así que no se necesitan importaciones. El programa se presenta con los resultados esperados; se revisó, pero no se ejecutó con un compilador de Swift durante esta investigación.
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)")
}
Qué consiguen las comprobaciones
Las comprobaciones de valores finitos rechazan NaN e infinito antes de que cualquiera de ellos se convierta en un recuento. Apple especifica que isFinite es falso para ambos. Exigir dimensiones de mosaico positivas también evita la división por cero y los tamaños de mosaico negativos.
También importa comprobar por separado el cociente, incluso después de validar las entradas. La implementación rechaza los cocientes no finitos y rechaza el valor cero cuando la extensión era positiva. Esta última comprobación hace que un cociente positivo extremadamente pequeño que se reduce a cero por subdesbordamiento provoque un error explícito en lugar de generar un eje vacío. Es un límite deliberado del ejemplo.
Después de convertir correctamente cada eje, todavía queda por comprobar el total. Dos valores Int válidos pueden tener un producto fuera del rango del tipo. La documentación de Apple sobre multipliedReportingOverflow(by:) especifica que partialValue contiene el producto completo solo cuando overflow es falso. El programa comprueba esa marca antes de usar el producto.
La comparación final maxCells tiene otro propósito: impone un límite definido por la aplicación, incluso cuando la operación aritmética cabe en el tipo. El valor ilustrativo de 10.000 es una política elegida por quien llama a la función, no una recomendación sobre el rendimiento de la memoria o del dibujo. Elige un límite según lo que la aplicación cree realmente para cada mosaico.
Evita comprobar el rango con coma flotante en el límite del entero
Una comprobación tentadora es value <= Double(Int.max) seguida de Int(value). Esa comparación puede inducir a error porque la conversión del límite entero a coma flotante puede redondearlo. Apple describe explícitamente Double.init(_:) a partir de Int como una conversión a la representación más cercana posible.
En una plataforma de 64 bits, Int.max es 9_223_372_036_854_775_807. Al convertirlo a Double, se redondea a 9_223_372_036_854_775_808, que es una unidad mayor. Un valor de coma flotante igual a ese límite redondeado superaría la comparación, aunque seguiría fuera del rango entero. Apple documenta la relación entre el ancho de la plataforma y el tamaño del entero en su página de referencia de Int.
Este ejemplo del límite es una ilustración aritmética, no un tamaño de lienzo propuesto. Explica por qué el programa pide a Int(exactly:) que compruebe directamente si el valor se puede representar. También evita codificar de forma fija un recuento máximo vinculado a un ancho de entero concreto.
Decide cómo tratar los valores próximos al límite de un mosaico
El redondeo hacia arriba sigue el valor realmente almacenado. Un cociente apenas superior a dos requiere tres mosaicos según esta política de cobertura. La propiedad nextUp de Swift proporciona el siguiente valor de coma flotante representable, por lo que (2.0).nextUp.rounded(.up) es 3.0. Apple documenta el significado de ese valor adyacente en la referencia de nextUp.
No restes un épsilon arbitrario solo para que un valor mostrado parezca entero. Eso cambia la política de cobertura y puede eliminar un mosaico necesario para un pequeño resto real. Si la aplicación ajusta intencionadamente las coordenadas a una cuadrícula, define esa regla de ajuste en un paso anterior y calcula los recuentos a partir de la geometría ajustada. Si las dimensiones proceden de unidades enteras exactas, considera conservarlas durante el cálculo del recuento.
El ejemplo también supone que restar las coordenadas proporcionadas da extensiones útiles. Una conversión final exacta no puede recuperar detalles que ya se hayan perdido en operaciones aritméticas anteriores con coordenadas. Mantén las magnitudes de las coordenadas adecuadas para la distribución y usa las mismas unidades para las dimensiones del lienzo y de los mosaicos.
Comprueba los casos que distinguen un comportamiento correcto
Usa casos límite para verificar tu adaptación, sobre todo si cambias la política de redondeo o el comportamiento de la cuadrícula vacía. Los siguientes resultados esperados se derivan del contrato indicado por este programa; no corresponden a ejecuciones de pruebas en Swift.
| Entrada ficticia | Resultado esperado |
|---|---|
Lienzo 650 × 410, mosaicos 64 × 64, límite 10_000 |
11 columnas, 7 filas, 77 mosaicos |
Lienzo 128 × 128, mosaicos 64 × 64, límite 10_000 |
2 columnas, 2 filas, 4 mosaicos |
Lienzo 129 × 128, mosaicos 64 × 64, límite 10_000 |
3 columnas, 2 filas, 6 mosaicos |
| Cualquiera de las extensiones del lienzo es cero; las demás entradas son válidas | Cuadrícula vacía con un total de cero |
| Extensión negativa, dimensión de mosaico cero o entrada no finita | invalidInput |
| La extensión positiva dividida por el tamaño de un mosaico produce cero o infinito | unrepresentableAxis |
Un eje redondeado hacia arriba no cabe en Int |
unrepresentableAxis |
| Recuentos válidos de los ejes cuyo producto produce desbordamiento | countOverflow |
El ejemplo de 77 mosaicos con límite 50 |
exceedsLimit |
Para este lienzo de Mac, conserva la geometría en coma flotante hasta haber calculado cada eje, redondea hacia arriba para incluir los mosaicos parciales de los bordes y convierte mediante Int(exactly:). Después, valida la multiplicación y el límite de la aplicación antes de crear la cuadrícula. Así, las dimensiones calculadas se convierten en un recuento con un comportamiento ante errores explícito y fácil de revisar.
Fuentes
- FloatingPointRoundingRule — Apple
- rounded(_:) — Apple
- BinaryInteger.init(_:) — Apple
- Int.init(exactly:) — Apple
- isFinite — Apple
- multipliedReportingOverflow(by:) — Apple
- Double.init(_:) — Apple
- Int — Apple
- nextUp — Apple