Untuk kanvas Mac yang mencakup koordinat sangat besar dan detail yang sangat kecil, simpan lokasi kasar secara terpisah dari offset lokal. Kurangi titik asal tampilan sebelum menerapkan zoom, lalu periksa presisi pada setiap konversi. Tutorial ini menyusun perhitungan tersebut menggunakan data adegan sintetis.
Tugas konkretnya adalah memosisikan penanda sintetis pada kanvas desktop yang dapat diperbesar tanpa kehilangan pergeseran kecilnya karena alamat adegannya secara keseluruhan besar. Contoh ini menangani satu sumbu; terapkan perhitungan yang sama secara terpisah pada sumbu lainnya. Hasilnya berupa koordinat gambar numerik agar Anda dapat memeriksa perhitungannya sebelum menghubungkannya ke kanvas.
Mengapa gerakan kecil menghilang pada koordinat besar?
Double di Swift adalah tipe titik mengambang 64-bit. Rentangnya mencakup nilai besar dan kecil, tetapi itu tidak berarti tipe ini dapat mempertahankan setiap kenaikan kecil yang ditambahkan ke nilai besar. Apple mendokumentasikan tipe dan API konversinya di Double.
Presisi ganda biner memiliki 53 digit biner signifikan. Akibatnya, jarak antara nilai yang dapat direpresentasikan bertambah seiring besarnya nilai. Referensi aritmetika Goldberg menjelaskan representasi ini dan bagaimana pengurangan operand yang sebelumnya telah dibulatkan dapat menyingkap informasi yang hilang lebih awal. Yang Perlu Diketahui Setiap Ilmuwan Komputer tentang Aritmetika Titik Mengambang
Pertimbangkan masukan ilustratif berikut: indeks sel 2^40, lebar sel 1024 unit adegan, dan offset penanda 2^-30 unit adegan. Menggabungkan alamat sel dan lebarnya menghasilkan koordinat absolut 2^50. Pada koordinat positif tersebut, nilai Double yang bersebelahan berjarak 0.25 unit. Penambahan offset yang sangat kecil itu dibulatkan kembali ke koordinat semula.
Karena itu, pengurangan titik asal setelahnya menghasilkan nol. Mengalikan nol tersebut dengan faktor zoom yang besar tidak dapat memulihkan offset. Informasi itu hilang saat koordinat absolut disusun.
Gunakan x.ulp untuk memeriksa jarak pada magnitudo yang benar-benar Anda simpan. Apple mendefinisikannya sebagai unit pada posisi digit terakhir dan mendokumentasikan pengecualiannya, termasuk nilai nonhingga. Double.ulpOfOne menjelaskan jarak pada nilai satu; ini bukan toleransi absolut universal untuk koordinat adegan. ulp
Informasi apa yang harus dipertahankan oleh representasi koordinat?
Untuk kanvas ini, representasikan setiap sumbu dengan indeks sel Int64 dan offset lokal Double. Secara konseptual, posisi berarti cell × cellSize + local, tetapi perhitungannya harus menghindari pembentukan nilai titik mengambang absolut tersebut.
Pertahankan penanda dan titik asal tampilan dalam bentuk terpisah ini sejak awal. Mengonversi koordinat absolut yang sudah dibulatkan menjadi sel dan offset tidak dapat memulihkan detail yang hilang.
Hal-hal berikut adalah asumsi desain untuk contoh ini, bukan batas numerik universal:
- Setiap sel lebarnya tepat
1024unit adegan. - Indeks sel menggunakan
Int64; indeks tersebut adalah alamat atau hitungan, bukan kuantitas titik mengambang. - Setiap offset lokal bernilai hingga dan berada dalam rentang
0..<1024. - Pemosisian terperinci hanya menerima sel titik asal dan sel tetangganya.
- Zoom adalah angka hingga dan positif yang menyatakan unit tampilan per unit adegan.
- Titik asal layar adalah koordinat gambar sintetis
512.
Hitung koordinat relatif sebagai cellDifference × cellSize + localDifference. Yang penting, kurangi indeks sel bilangan bulat sebelum mengonversi selisihnya ke Double. Konversi bilangan bulat biasa dari Apple dapat membulatkan; Double(exactly:) justru gagal jika representasi yang tepat tidak tersedia. Double
Bahkan pengurangan dua nilai Int64 yang valid dapat melampaui rentang tipe tersebut. Gunakan subtractingReportingOverflow(_:) dan periksa tandanya sebelum menggunakan hasilnya. Apple secara eksplisit mendokumentasikan bahwa hasil parsial dipotong saat terjadi luapan. subtractingReportingOverflow(_:)
Tata letak ini mempertahankan alamat besar secara terpisah dari detail lokal. Presisinya tetap bergantung pada offset lokal: kenaikan yang sangat kecil dapat hilang saat ditambahkan ke offset lokal yang cukup besar. Pilih lebar sel dan titik jangkar lokal sesuai detail terkecil yang dibutuhkan tampilan saat ini.
Jalankan perhitungan lokal lengkap di Mac Anda
Anda memerlukan toolchain Swift yang berfungsi. Jika perlu, ikuti petunjuk terbaru Instal Swift — macOS. Di Terminal, periksa instalasi lalu buat paket kecil yang dapat dieksekusi menggunakan alur kerja yang didokumentasikan dalam Membangun Alat Baris Perintah.
swift --version
mkdir SceneCoordinates
cd SceneCoordinates
swift package init --name SceneCoordinates --type executable
Di target executable yang dihasilkan dalam Sources, ganti file Swift titik masuknya dengan program lengkap di bawah ini. Gunakan satu file titik masuk yang memuat program ini, tanpa pernyataan tingkat atas tambahan. Program ini hanya mengimpor pustaka standar Swift dan tidak memerlukan dependensi paket.
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)
}
}
Jalankan paket dari direktorinya:
swift run SceneCoordinates
Hasil numerik yang diharapkan berturut-turut adalah 0.25, 0.0, 513.0, dan 513.0. Hasil ini diturunkan dari masukan sintetis yang diberikan. Jarak binary64, pergeseran yang lenyap, dan penskalaan pangkat dua telah diperiksa dengan Python di lingkungan riset; compiler Swift tidak tersedia di sana, jadi program Swift telah ditinjau tetapi tidak dijalankan.
Hasil stabil pertama mempertahankan 2^-30 secara terpisah dari alamat sel yang sangat besar, lalu menskalakannya sebesar 2^30 untuk menghasilkan satu unit tampilan. Hasil kedua menggunakan 2^-600 dengan zoom 2^600; kedua masukan dan hasil kalinya dapat direpresentasikan dalam contoh ini. Magnitudo kecil saja tidak berarti presisi hilang.
Program ini menggunakan a.addingProduct(b, c) untuk a + b × c. Apple mendokumentasikannya sebagai operasi gabungan tanpa pembulatan perantara. Operasi ini mengurangi pembulatan di dalam operasi tersebut; pengurangan lokal dan pembulatan masukan sebelumnya tetap terpisah. addingProduct(::)
Bagaimana memilih anggaran presisi yang berguna?
Mulailah dari jarak terkecil yang harus tetap terlihat pada zoom saat ini. Untuk target ilustratif sebesar seperempat unit tampilan, bagi 0.25 dengan zoom untuk mendapatkan anggaran galat dalam ruang adegan. Lalu periksa jarak pada koordinat lokal dan nilai perantara yang digunakan dalam perhitungan.
Contohnya, pada zoom 2^30, anggaran ilustratifnya adalah 2^-32 unit adegan. Pada koordinat lokal 512, jarak Double adalah 2^-43, jauh lebih halus daripada anggaran tersebut. Pada koordinat absolut 2^50, jaraknya adalah 0.25, terlalu kasar. Nilai hasil perhitungan ini menjelaskan mengapa titik jangkar lokal yang berdekatan membantu tampilan khusus ini.
Jarak adalah alat diagnosis, bukan batas galat yang lengkap. Konversi masukan, pengurangan, transformasi, dan konversi gambar akhir masing-masing dapat menambah galat. Uji pergeseran yang diketahui melalui seluruh jalur, termasuk renderer yang sebenarnya, sebelum menetapkan rentang zoom.
| Pengamatan | Kemungkinan masalah numerik | Tindakan berikutnya |
|---|---|---|
| Penambahan offset tidak mengubah koordinat yang tersimpan | Jarak penyimpanan melebihi kenaikannya | Pertahankan offset secara terpisah atau pilih titik jangkar yang lebih dekat |
| Koordinat relatif berfungsi hingga tahap konversi | Presisi tujuan tidak memadai | Lakukan konversi setelah pengurangan titik asal dan zoom; periksa galat hasilnya |
| Nilai menjadi tak hingga | Suatu operasi melampaui rentang hingga | Ubah urutan evaluasi atau batasi tampilan yang didukung |
| Pergeseran bukan nol menjadi nol | Underflow atau pembulatan menghapusnya | Skala ulang lebih awal atau ubah representasi lokal |
| Sel yang jauh ditolak oleh program ini | Kebijakan tampilan terperinci telah terlampaui | Pilih titik asal yang lebih dekat atau gunakan perhitungan ikhtisar kasar yang terpisah |
Untuk tampilan ikhtisar, beberapa detail kecil memang dapat dipetakan ke nilai yang jauh di bawah satu unit tampilan. Tetapkan perilaku itu secara eksplisit. Pembatasan sel tetangga dalam contoh ini sengaja membatasi aritmetika terperinci; memperluasnya memerlukan anggaran presisi baru, bukan sekadar menghapus pemeriksaan.
Apa yang terjadi pada skala yang sangat kecil dan saat menggambar?
Apple membedakan leastNormalMagnitude dari nilai subnormal positif yang lebih kecil. Subnormal memiliki presisi yang lebih rendah daripada nilai normal, sehingga bilangan positif terkecil bukanlah skala kerja bawaan yang baik. Periksa hasil antara selain masukan saat mengalikan offset kecil dengan zoom. leastNormalMagnitude
Contoh ini menolak pergeseran bukan nol yang setelah diskalakan menjadi nol atau tidak lagi dapat dibedakan dari titik asal layar. Contoh ini tidak memastikan bahwa setiap hasil bukan nol memenuhi toleransi visual. Tambahkan pemeriksaan penerimaan berbasis toleransi yang sesuai untuk kanvas Anda, dan tangani galat yang dilempar secara eksplisit alih-alih menggantinya dengan koordinat titik asal.
Untuk kanvas dua dimensi, pertahankan posisi terpisah untuk setiap sumbu dan hitung kedua koordinat relatif sebelum menggambar. Untuk perluasan tiga dimensi, Apple menyediakan simd_double3, alias dari SIMD3<Double>. Setiap komponen tetap berupa nilai titik mengambang 64-bit dengan batas presisinya sendiri. simd_double3
Pertahankan keadaan adegan terpisah sebagai posisi acuan. Konversikan hanya koordinat gambar akhir yang berdekatan ke tipe yang diperlukan API gambar Anda, lalu ukur galat konversi terhadap anggaran ruang tampilan. Jika nilai akhir tersebut masih sangat besar, tentukan terlebih dahulu apakah objeknya termasuk dalam area pandang saat ini.
Kanvas yang stabil memerlukan rentang dukungan yang dinyatakan: alamat sel bilangan bulat, offset lokal yang dibatasi, rentang zoom, dan galat ruang tampilan yang dapat diterima. Pertahankan detail sebelum menyusun nilai besar, hitung relatif terhadap titik asal yang berdekatan, dan verifikasi pergeseran yang mewakili pada batas rentang tersebut.
Sumber
- Double — Dokumentasi Pengembang Apple
- ulp — Dokumentasi Pengembang Apple
- subtractingReportingOverflow(_:) — Dokumentasi Pengembang Apple
- addingProduct(::) — Dokumentasi Pengembang Apple
- leastNormalMagnitude — Dokumentasi Pengembang Apple
- simd_double3 — Dokumentasi Pengembang Apple
- Instal Swift — Swift.org
- Membangun Alat Baris Perintah — Swift.org
- Yang Perlu Diketahui Setiap Ilmuwan Komputer tentang Aritmetika Titik Mengambang — Oracle