Para um canvas no Mac que abrange coordenadas enormes e detalhes minúsculos, armazene separadamente as posições gerais e os deslocamentos locais. Subtraia a origem da visualização antes de aplicar o zoom e verifique a precisão em cada conversão. Este tutorial monta esse cálculo usando dados sintéticos de cena.
A tarefa concreta é posicionar um marcador sintético em um canvas de desktop com zoom sem perder seu pequeno deslocamento por causa do endereço geral da cena ser grande. O exemplo trata de um eixo; aplique o mesmo cálculo independentemente ao outro eixo. Ele produz coordenadas numéricas de desenho para que você possa inspecionar a aritmética antes de conectá-la a um canvas.
Por que um movimento minúsculo desaparece em uma coordenada grande?
O Double do Swift é um tipo de ponto flutuante de 64 bits. Seu intervalo permite magnitudes grandes e pequenas, mas isso não significa que ele consiga preservar todo incremento minúsculo somado a um valor grande. A Apple documenta o tipo e suas APIs de conversão em Double.
A precisão dupla binária tem 53 dígitos binários significativos. Consequentemente, o espaçamento entre valores representáveis aumenta com a magnitude. A referência de Goldberg sobre aritmética explica tanto essa representação quanto como subtrair operandos previamente arredondados pode expor informações perdidas antes. O que todo cientista da computação deve saber sobre aritmética de ponto flutuante
Considere estas entradas explicitamente ilustrativas: um índice de célula de 2^40, uma largura de célula de 1024 unidades de cena e um deslocamento do marcador de 2^-30 unidades de cena. Combinar o endereço da célula e a largura resulta em uma coordenada absoluta de 2^50. Nessa coordenada positiva, valores Double adjacentes ficam separados por 0.25 unidades. Somar o minúsculo deslocamento arredonda o resultado de volta à coordenada original.
Subtrair a origem depois, portanto, resulta em zero. Multiplicar esse zero por um fator de zoom grande não recupera o deslocamento. A perda ocorreu quando a coordenada absoluta foi montada.
Use x.ulp para inspecionar o espaçamento na magnitude que você realmente armazena. A Apple define isso como a unidade no último lugar e documenta suas exceções, incluindo valores não finitos. Double.ulpOfOne descreve o espaçamento em um; não é uma tolerância absoluta universal para coordenadas de cena. ulp
O que a representação das coordenadas deve preservar?
Para este canvas, represente cada eixo com um índice de célula Int64 e um deslocamento local Double. Conceitualmente, uma posição significa cell × cellSize + local, mas o cálculo deve evitar construir esse valor absoluto de ponto flutuante.
Mantenha o marcador e a origem da visualização nesse formato dividido desde o início. Converter uma coordenada absoluta já arredondada em uma célula e um deslocamento não recria os detalhes que faltam.
As condições a seguir são premissas de projeto para o exemplo, não limites numéricos universais:
- Cada célula tem exatamente
1024unidades de cena de largura. - Os índices de célula usam
Int64; são endereços ou contagens, não quantidades de ponto flutuante. - Cada deslocamento local é finito e está no intervalo
0..<1024. - O posicionamento detalhado aceita somente a célula de origem e suas vizinhas imediatas.
- O zoom é um número finito e positivo de unidades de exibição por unidade de cena.
- A origem da tela é a coordenada sintética de desenho
512.
Calcule a coordenada relativa como cellDifference × cellSize + localDifference. É crucial subtrair os índices inteiros das células antes de converter a diferença para Double. A conversão inteira comum da Apple pode arredondar; Double(exactly:) falha quando a representação exata não está disponível. Double
Até mesmo subtrair dois valores Int64 válidos pode exceder o intervalo desse tipo. Use subtractingReportingOverflow(_:) e inspecione seu sinalizador antes de usar o resultado. A Apple documenta explicitamente que, quando ocorre estouro, o resultado parcial é truncado. subtractingReportingOverflow(_:)
Esse formato preserva um endereço grande independentemente dos detalhes locais. A precisão ainda depende dos deslocamentos locais: um incremento muito minúsculo pode desaparecer quando somado a um deslocamento local suficientemente grande. Escolha larguras de célula e âncoras locais de acordo com o menor detalhe de que a visualização atual precisa.
Execute um cálculo local completo no Mac
Você precisa de uma toolchain Swift funcional. Se necessário, siga as instruções atuais de Instalar Swift — macOS. No Terminal, verifique a instalação e crie um pequeno pacote executável usando o fluxo de trabalho documentado em Criar uma ferramenta de linha de comando.
swift --version
mkdir SceneCoordinates
cd SceneCoordinates
swift package init --name SceneCoordinates --type executable
No destino executável gerado em Sources, substitua o arquivo Swift de ponto de entrada pelo programa completo abaixo. Use um único arquivo de ponto de entrada com este programa, sem instruções adicionais no nível superior. Ele importa somente a biblioteca padrão do Swift e não exige dependências de pacote.
import Swift
struct AxisPosition {
let cell: Int64
let local: Double
}
enum CoordinateError: Error {
case invalidInput
case cellDifferenceOverflow
case outsideDetailedView
case inexactCellConversion
case unrepresentableResult
}
func screenCoordinate(
point: AxisPosition,
origin: AxisPosition,
zoom: Double
) throws -> Double {
let cellSize = 1024.0
let screenOrigin = 512.0
guard point.local.isFinite,
origin.local.isFinite,
point.local >= 0, point.local < cellSize,
origin.local >= 0, origin.local < cellSize,
zoom.isFinite, zoom > 0 else {
throw CoordinateError.invalidInput
}
let difference = point.cell.subtractingReportingOverflow(origin.cell)
guard !difference.overflow else {
throw CoordinateError.cellDifferenceOverflow
}
guard (-1...1).contains(difference.partialValue) else {
throw CoordinateError.outsideDetailedView
}
guard let cells = Double(exactly: difference.partialValue) else {
throw CoordinateError.inexactCellConversion
}
let localDifference = point.local - origin.local
let delta = localDifference.addingProduct(cells, cellSize)
let scaled = delta * zoom
guard delta.isFinite, scaled.isFinite,
delta == 0 || scaled != 0 else {
throw CoordinateError.unrepresentableResult
}
let screen = screenOrigin.addingProduct(delta, zoom)
guard screen.isFinite,
delta == 0 || screen != screenOrigin else {
throw CoordinateError.unrepresentableResult
}
return screen
}
@main
struct SceneCoordinates {
static func main() throws {
let cell: Int64 = 1_099_511_627_776
let step = Double(sign: .plus, exponent: -30, significand: 1)
let zoom = Double(sign: .plus, exponent: 30, significand: 1)
let absoluteBase = Double(cell) * 1024.0
let collapsed = (absoluteBase + step) - absoluteBase
let screen = try screenCoordinate(
point: AxisPosition(cell: cell, local: step),
origin: AxisPosition(cell: cell, local: 0),
zoom: zoom
)
let tiny = Double(sign: .plus, exponent: -600, significand: 1)
let largeZoom = Double(sign: .plus, exponent: 600, significand: 1)
let tinyScreen = try screenCoordinate(
point: AxisPosition(cell: 0, local: tiny),
origin: AxisPosition(cell: 0, local: 0),
zoom: largeZoom
)
precondition(absoluteBase.ulp == 0.25)
precondition(collapsed == 0)
precondition(screen == 513)
precondition(tinyScreen == 513)
print("Absolute spacing:", absoluteBase.ulp)
print("Absolute displacement:", collapsed)
print("Split-coordinate screen:", screen)
print("Tiny-coordinate screen:", tinyScreen)
}
}
Execute o pacote a partir de seu diretório:
swift run SceneCoordinates
Os resultados numéricos esperados são 0.25, 0.0, 513.0 e 513.0, respectivamente. São resultados derivados das entradas sintéticas fornecidas. O espaçamento binário64 subjacente, o deslocamento colapsado e a escala em potência de dois foram verificados com Python no ambiente de pesquisa; não havia um compilador Swift disponível nesse ambiente, então o programa Swift foi inspecionado, mas não executado.
O primeiro resultado estável preserva 2^-30 separadamente do enorme endereço da célula e, em seguida, aplica a escala 2^30 para produzir uma unidade de exibição. O segundo usa 2^-600 com um zoom de 2^600; ambas as entradas e seu produto são representáveis neste exemplo. Uma magnitude pequena, por si só, não implica perda de precisão.
O programa usa a.addingProduct(b, c) para a + b × c. A Apple documenta isso como uma operação fundida sem arredondamento intermediário. Ela reduz o arredondamento dentro dessa operação; a subtração local e qualquer arredondamento anterior das entradas continuam sendo etapas separadas. addingProduct(::)
Como escolher um orçamento de precisão útil?
Comece pela menor separação que precisa continuar visível no zoom atual. Para uma meta ilustrativa de um quarto de unidade de exibição, divida 0.25 pelo zoom para obter um orçamento de erro no espaço da cena. Depois, inspecione o espaçamento nas coordenadas locais e nos valores intermediários usados no cálculo.
Por exemplo, no zoom 2^30, o orçamento ilustrativo é de 2^-32 unidades de cena. Na coordenada local 512, o espaçamento de Double é 2^-43, consideravelmente menor que esse orçamento. Na coordenada absoluta 2^50, o espaçamento é 0.25, grosseiro demais. Esses valores calculados explicam por que uma âncora local próxima ajuda esta visualização específica.
O espaçamento é um diagnóstico, não um limite de erro completo. A conversão das entradas, a subtração, as transformações e a conversão final para desenho podem contribuir para o erro. Teste deslocamentos conhecidos em todo o percurso, incluindo o renderizador real, antes de aceitar um intervalo de zoom.
| Observação | Provável problema numérico | Próxima ação |
|---|---|---|
| Somar um deslocamento não altera a coordenada armazenada | O espaçamento do armazenamento excede o incremento | Preserve o deslocamento separadamente ou escolha uma âncora mais próxima |
| As coordenadas relativas funcionam até a conversão | A precisão do destino é insuficiente | Converta depois de subtrair a origem e aplicar o zoom; verifique o erro resultante |
| Os valores se tornam infinitos | Uma operação excede o intervalo finito | Altere a ordem de avaliação ou limite a visualização compatível |
| Um deslocamento diferente de zero se torna zero | Subfluxo ou arredondamento o apagou | Reescale antes ou altere a representação local |
| Este programa rejeita uma célula distante | A política de visualização detalhada foi excedida | Selecione uma origem próxima ou use um cálculo separado de visão geral |
Em uma visão geral, alguns detalhes minúsculos podem legitimamente corresponder a muito menos que uma unidade de exibição. Defina esse comportamento explicitamente. A restrição a células vizinhas no exemplo limita intencionalmente a aritmética detalhada; ampliá-la exige um novo orçamento de precisão, em vez de simplesmente remover a verificação.
O que acontece em escalas muito pequenas e durante o desenho?
A Apple distingue leastNormalMagnitude dos valores subnormais positivos menores. Os subnormais têm precisão reduzida em relação aos valores normais, portanto o menor número positivo não é uma boa escala de trabalho padrão. Verifique os resultados intermediários, além das entradas, ao multiplicar deslocamentos minúsculos pelo zoom. leastNormalMagnitude
O exemplo rejeita um deslocamento diferente de zero que, após ser escalado, se torne zero ou indistinguível da origem da tela. Ele não certifica todos os resultados diferentes de zero em relação a uma tolerância visual. Adicione uma verificação de aceitação baseada em tolerância, adequada ao seu canvas, e trate erros lançados explicitamente em vez de substituir o resultado pela coordenada de origem.
Para um canvas bidimensional, preserve uma posição dividida para cada eixo e calcule ambas as coordenadas relativas antes de desenhar. Para uma extensão tridimensional, a Apple oferece simd_double3, um alias de SIMD3<Double>. Cada componente continua sendo um valor de ponto flutuante de 64 bits com seu próprio limite de precisão. simd_double3
Mantenha o estado dividido da cena como posição de referência. Converta somente as coordenadas finais de desenho próximas para o tipo exigido pela API de desenho e meça o erro de conversão em relação ao orçamento no espaço da tela. Se esses valores finais ainda forem enormes, primeiro determine se o objeto pertence à janela de visualização atual.
Um canvas estável precisa de um intervalo compatível declarado: endereços de células inteiros, deslocamentos locais limitados, um intervalo de zoom e um erro aceitável no espaço da tela. Preserve os detalhes antes de montar valores grandes, calcule em relação a uma origem próxima e verifique deslocamentos representativos nos limites desse intervalo.
Fontes
- Double — Documentação para desenvolvedores da Apple
- ulp — Documentação para desenvolvedores da Apple
- subtractingReportingOverflow(_:) — Documentação para desenvolvedores da Apple
- addingProduct(::) — Documentação para desenvolvedores da Apple
- leastNormalMagnitude — Documentação para desenvolvedores da Apple
- simd_double3 — Documentação para desenvolvedores da Apple
- Instalar Swift — Swift.org
- Criar uma ferramenta de linha de comando — Swift.org
- O que todo cientista da computação deve saber sobre aritmética de ponto flutuante — Oracle