Para transformar um tamanho de grade calculado em uma contagem inteira no Swift, escolha primeiro a regra de arredondamento e, em seguida, use Int(exactly:) para verificar se o valor pode ser representado. Em uma área de desenho para Mac coberta por peças, arredonde cada eixo para cima e verifique se a multiplicação final causa estouro.

Este artigo desenvolve esse cálculo para uma ferramenta de desenho para desktop: dada uma área de desenho retangular e peças de tamanho fixo, determinar quantas colunas, linhas e peças são necessárias para cobri-la. Todas as coordenadas e contagens abaixo são fictícias. A grade começa na origem da própria área de desenho, não tem espaços entre as peças e permite peças parciais nas bordas externas.

Escolha a regra de arredondamento de acordo com a finalidade da grade

Uma dimensão fracionária da grade precisa de uma política antes de precisar de uma conversão de tipo. Se uma área de desenho tem 650 pontos de largura e cada peça tem 64 pontos de largura, a dimensão calculada é 650.0 / 64.0, ou 10.15625. Para cobrir toda a largura, são necessárias 11 colunas. Contando apenas as peças inteiras que cabem nessa largura, são 10.

O Swift oferece regras explícitas de arredondamento para essas diferentes tarefas. .up arredonda em direção ao infinito positivo, .down em direção ao infinito negativo e .towardZero remove a parte fracionária. O arredondamento para o valor mais próximo tem regras específicas para empates. Esses comportamentos estão definidos na documentação de FloatingPointRoundingRule da Apple.

Tarefa do leitor Regra para uma dimensão de eixo não negativa Resultado para 10.15625
Cobrir toda a extensão, incluindo uma peça na borda .up 11.0
Contar as peças inteiras que cabem na extensão .down 10.0
Escolher a dimensão inteira mais próxima .toNearestOrAwayFromZero 10.0
Exigir que a dimensão calculada já seja inteira Sem arredondamento; tente diretamente Int(exactly:) nil

Para a tarefa de cobertura, use .rounded(.up). Chamar .rounded() sem uma regra seleciona o arredondamento para o valor mais próximo, com empates afastados de zero, o que pode deixar parte desta área de desenho descoberta. Além disso, arredondar um Double retorna um Double com valor inteiro; a conversão para inteiro continua sendo uma etapa separada. Consulte a documentação de rounded(_:) da Apple.

Arredonde as colunas e as linhas separadamente

Suponha que a área de desenho sintética se estenda da coordenada horizontal 20 à 670 e da coordenada vertical 30 à 440. Sua largura é de 650 pontos e sua altura é de 410 pontos. Com peças quadradas de 64 pontos de lado, o cálculo de cobertura é:

  • Colunas: ceil(650 / 64) = 11.
  • Linhas: ceil(410 / 64) = 7.
  • Total: 11 × 7 = 77 peças.

Esses são resultados aritméticos ilustrativos, derivados do layout especificado. Dividir a área da tela pela área da peça e arredondar uma única vez resulta em 66, o que não representa o arranjo retangular necessário. Cada linha precisa de 11 posições para peças, e a área de desenho precisa de sete dessas linhas. Preserve essas duas dimensões até arredondar ambas.

Esta fórmula pressupõe que a primeira peça começa na origem da área de desenho. Se as peças pertencerem a uma grade ancorada em outro lugar, o deslocamento inicial poderá alterar quantas células o retângulo cruza. Defina esse alinhamento antes de aplicar um cálculo baseado apenas na largura.

Por que uma conversão direta para inteiro não basta

O inicializador comum do Swift para converter ponto flutuante em inteiro arredonda em direção a zero. Portanto, Int(10.15625) produz 10. O valor de origem também precisa caber no tipo inteiro de destino após o truncamento; a Apple documenta que uma conversão fora do intervalo pode causar um erro em tempo de execução. Consulte BinaryInteger.init(_:).

Para uma dimensão de cobertura calculada, a sequência útil é Int(exactly: value.rounded(.up)), seguida do tratamento opcional do resultado. A Apple documenta que Int.init(exactly:) retorna nil quando o valor de ponto flutuante fornecido não pode ser representado exatamente como um inteiro.

O inicializador responde a uma pergunta específica: esse valor fornecido cabe exatamente? Ele não confirma que a geometria original é válida, que a regra de arredondamento escolhida é adequada ao layout ou que criar essa quantidade de peças é viável. Essas são verificações separadas na implementação abaixo.

Um programa Swift completo para uma contagem limitada de peças

O programa a seguir usa Double para coordenadas e dimensões e Int para contagens. Ele exige extensões finitas e não negativas, dimensões de peça finitas e positivas e um limite de peças positivo definido pela aplicação. Uma área de desenho em que qualquer uma das extensões seja zero produz uma grade vazia. Limites invertidos são rejeitados em vez de serem reorganizados silenciosamente.

Salve este programa completo como GridCount.swift e execute-o com swift GridCount.swift em um Mac com uma toolchain do Swift disponível. Ele usa apenas a biblioteca padrão do Swift, portanto não exige imports. O programa é apresentado com os resultados esperados; foi inspecionado, mas não executado com um compilador Swift durante esta pesquisa.

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)")
}

O que as verificações garantem

As verificações de valores finitos rejeitam NaN e infinito antes que qualquer um deles possa se tornar uma contagem. A Apple especifica que isFinite é falso para ambos. Exigir dimensões de peça positivas também exclui a divisão por zero e tamanhos de peça negativos.

Verificar o quociente separadamente é importante mesmo depois de validar as entradas. A implementação rejeita um quociente não finito e também rejeita o valor zero quando a extensão era positiva. Essa última verificação faz com que um quociente positivo extremamente pequeno que sofre underflow até zero resulte em uma falha explícita, em vez de um eixo vazio. Esse é um limite deliberado do exemplo.

Mesmo após a conversão bem-sucedida de cada eixo, ainda é preciso verificar o total. Dois valores Int válidos podem ter um produto fora do intervalo do tipo. A documentação de multipliedReportingOverflow(by:) da Apple especifica que partialValue contém o produto completo somente quando overflow é falso. O programa verifica essa sinalização antes de usar o produto.

A comparação final com maxCells tem outra finalidade: impor um limite da aplicação mesmo quando a operação aritmética não causa estouro. O valor ilustrativo de 10.000 é uma política escolhida por quem chama o código, não uma recomendação sobre memória ou desempenho de desenho. Escolha um limite com base no que a sua aplicação realmente cria para cada peça.

Evite uma verificação de intervalo em ponto flutuante no limite do inteiro

Uma verificação tentadora é value <= Double(Int.max) seguida de Int(value). Essa comparação pode induzir ao erro porque converter o limite inteiro para ponto flutuante pode arredondá-lo. A Apple descreve explicitamente Double.init(_:) a partir de Int como uma conversão para a representação possível mais próxima.

Em um destino de 64 bits, Int.max é 9_223_372_036_854_775_807. Sua conversão para Double arredonda para 9_223_372_036_854_775_808, que é um a mais. Um valor de ponto flutuante igual a esse limite arredondado passaria na comparação, embora ainda estivesse fora do intervalo do inteiro. A Apple documenta a relação entre a largura da plataforma e o tamanho do inteiro na página de referência de Int.

Este exemplo de limite é uma ilustração aritmética, não uma sugestão de tamanho para a área de desenho. Ele explica por que o programa pede a Int(exactly:) que faça diretamente a verificação de representabilidade. Também evita codificar um número máximo de contagem vinculado a uma largura inteira específica.

Decida como lidar com valores próximos ao limite de uma peça

O arredondamento para cima segue o valor efetivamente armazenado. Um quociente um pouco acima de dois exige três peças segundo esta política de cobertura. A propriedade nextUp do Swift fornece o próximo valor de ponto flutuante representável maior, portanto (2.0).nextUp.rounded(.up) é 3.0. O significado desse valor adjacente está documentado na referência de nextUp da Apple.

Não subtraia um épsilon arbitrário apenas para fazer um valor exibido parecer inteiro. Isso altera a política de cobertura e pode remover uma peça necessária para cobrir uma pequena sobra real. Se a aplicação ajustar intencionalmente as coordenadas à grade, defina essa regra de ajuste antes e calcule as contagens com base na geometria ajustada. Se as dimensões vierem de unidades inteiras exatas, considere manter essas unidades durante todo o cálculo das contagens.

O exemplo também pressupõe que a subtração das coordenadas fornecidas produz extensões úteis. Uma conversão final exata não pode recuperar detalhes já perdidos em operações aritméticas anteriores com coordenadas. Mantenha as magnitudes das coordenadas adequadas ao layout e use as mesmas unidades para as dimensões da área de desenho e das peças.

Verifique os casos que diferenciam o comportamento correto

Use casos-limite para verificar sua adaptação, especialmente se você alterar a política de arredondamento ou o comportamento da grade vazia. Os resultados esperados a seguir seguem o contrato declarado por este programa; eles não correspondem a execuções de testes em Swift.

Entrada sintética Resultado esperado
Área de desenho 650 × 410, peças 64 × 64, limite 10_000 11 colunas, 7 linhas, 77 peças
Área de desenho 128 × 128, peças 64 × 64, limite 10_000 2 colunas, 2 linhas, 4 peças
Área de desenho 129 × 128, peças 64 × 64, limite 10_000 3 colunas, 2 linhas, 6 peças
Uma das extensões da área de desenho é zero; as demais entradas são válidas Grade vazia com total igual a zero
Extensão negativa, dimensão de peça zero ou entrada não finita invalidInput
A divisão de uma extensão positiva pela dimensão da peça resulta em zero ou infinito unrepresentableAxis
Um eixo arredondado para cima não cabe em Int unrepresentableAxis
As contagens válidas dos eixos têm um produto que causa estouro countOverflow
O exemplo de 77 peças com limite 50 exceedsLimit

Para esta área de desenho para Mac, mantenha a geometria em ponto flutuante até calcular cada eixo, arredonde para cima para incluir as peças parciais nas bordas e faça a conversão por meio de Int(exactly:). Em seguida, valide a multiplicação e o limite da aplicação antes de criar a grade. Essa sequência transforma as dimensões calculadas em uma contagem com um comportamento de falha explícito e fácil de analisar.

Fontes