Untuk kanvas Mac yang merangkumi koordinat yang sangat besar dan butiran yang sangat kecil, simpan lokasi kasar secara berasingan daripada ofset setempat. Tolak asal paparan sebelum menggunakan zum, dan semak kejituan pada setiap penukaran. Tutorial ini membina pengiraan tersebut menggunakan data adegan sintetik.

Tugas khususnya ialah meletakkan penanda sintetik pada kanvas desktop yang boleh dizum tanpa kehilangan anjakan kecilnya akibat alamat adegan keseluruhannya yang besar. Contoh ini mengendalikan satu paksi; gunakan pengiraan yang sama secara berasingan pada paksi yang satu lagi. Ia menghasilkan koordinat lukisan berangka supaya anda boleh memeriksa aritmetik sebelum menghubungkannya ke kanvas.

Mengapa pergerakan kecil hilang pada koordinat yang besar?

Double dalam Swift ialah jenis titik apung 64-bit. Julatnya membolehkan magnitud besar dan kecil, tetapi ini tidak bermakna ia boleh mengekalkan setiap kenaikan kecil yang ditambah pada nilai besar. Apple mendokumenkan jenis ini dan API penukarannya dalam Double.

Ketepatan ganda binari mempunyai 53 digit binari bererti. Oleh itu, jarak antara nilai yang boleh diwakili bertambah mengikut magnitud. Rujukan aritmetik Goldberg menerangkan perwakilan ini serta cara penolakan operan yang telah dibundarkan boleh mendedahkan maklumat yang hilang lebih awal. Perkara yang Patut Diketahui oleh Setiap Ahli Sains Komputer tentang Aritmetik Titik Apung

Pertimbangkan input ilustrasi yang dinyatakan dengan jelas ini: indeks sel 2^40, lebar sel 1024 unit adegan, dan ofset penanda 2^-30 unit adegan. Menggabungkan alamat sel dan lebar menghasilkan koordinat mutlak 2^50. Pada koordinat positif itu, nilai Double bersebelahan berselang 0.25 unit. Penambahan ofset kecil itu dibundarkan kembali kepada koordinat asal.

Oleh itu, penolakan asal selepas itu menghasilkan sifar. Mendarabkan sifar itu dengan faktor zum yang besar tidak dapat memulihkan ofset tersebut. Kehilangan berlaku apabila koordinat mutlak dibentuk.

Gunakan x.ulp untuk memeriksa jarak pada magnitud yang benar-benar anda simpan. Apple mentakrifkannya sebagai unit pada tempat terakhir dan mendokumenkan pengecualiannya, termasuk nilai bukan terhingga. Double.ulpOfOne menerangkan jarak pada nilai satu; ia bukan toleransi mutlak sejagat untuk koordinat adegan. ulp

Apakah yang patut dikekalkan oleh perwakilan koordinat?

Untuk kanvas ini, wakilkan setiap paksi dengan indeks sel Int64 dan ofset setempat Double. Secara konsep, kedudukan bermaksud cell × cellSize + local, tetapi pengiraan harus mengelakkan pembentukan nilai titik apung mutlak itu.

Kekalkan penanda dan asal paparan dalam bentuk terpisah ini sejak awal. Menukar koordinat mutlak yang sudah dibundarkan menjadi sel dan ofset tidak dapat mencipta semula butiran yang hilang.

Andaian berikut ialah andaian reka bentuk bagi contoh kerja ini, bukannya had angka sejagat:

  • Setiap sel selebar tepat 1024 unit adegan.
  • Indeks sel menggunakan Int64; indeks ialah alamat atau kiraan, bukan kuantiti titik apung.
  • Setiap ofset setempat adalah terhingga dan berada dalam julat 0..<1024.
  • Kedudukan terperinci hanya menerima sel asal dan sel berjiran terdekatnya.
  • Zum ialah nombor terhingga dan positif bagi unit paparan bagi setiap unit adegan.
  • Asal skrin ialah koordinat lukisan sintetik 512.

Kira koordinat relatif sebagai cellDifference × cellSize + localDifference. Yang penting, tolak indeks sel integer sebelum menukar perbezaannya kepada Double. Penukaran integer biasa Apple mungkin membundarkan; Double(exactly:) sebaliknya gagal apabila perwakilan tepat tidak tersedia. Double

Walaupun penolakan dua nilai Int64 yang sah boleh melebihi julat jenis itu. Gunakan subtractingReportingOverflow(_:) dan periksa benderanya sebelum menggunakan hasilnya. Apple mendokumenkan dengan jelas bahawa hasil separa dipotong apabila limpahan berlaku. subtractingReportingOverflow(_:)

Susun atur ini mengekalkan alamat besar secara berasingan daripada butiran setempat. Kejituannya masih bergantung pada ofset setempat: kenaikan yang sangat kecil boleh hilang apabila ditambah pada ofset setempat yang cukup besar. Pilih lebar sel dan sauh setempat berdasarkan butiran terkecil yang diperlukan oleh paparan semasa.

Jalankan pengiraan setempat yang lengkap pada Mac anda

Anda memerlukan rantai alat Swift yang berfungsi. Jika perlu, ikuti arahan semasa Pasang Swift — macOS. Dalam Terminal, semak pemasangan dan cipta pakej boleh laku kecil menggunakan aliran kerja yang didokumenkan dalam Bina Alat Baris Perintah.

swift --version
mkdir SceneCoordinates
cd SceneCoordinates
swift package init --name SceneCoordinates --type executable

Dalam sasaran boleh laku yang dijana di bawah Sources, gantikan fail Swift titik masuknya dengan program lengkap di bawah. Gunakan satu fail titik masuk yang mengandungi program ini, tanpa pernyataan peringkat atas tambahan. Program ini hanya mengimport pustaka standard Swift dan tidak memerlukan kebergantungan pakej.

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 pakej dari direktorinya:

swift run SceneCoordinates

Hasil berangka yang dijangka ialah 0.25, 0.0, 513.0, dan 513.0, masing-masing. Ini ialah hasil terbitan bagi input sintetik yang diberikan. Jarak binary64, anjakan yang terhapus, dan penskalaan kuasa dua asas telah disemak menggunakan Python dalam persekitaran penyelidikan; pengkompil Swift tidak tersedia di sana, jadi program Swift itu diperiksa tetapi tidak dijalankan.

Hasil stabil pertama mengekalkan 2^-30 secara berasingan daripada alamat sel yang sangat besar, kemudian menskalakannya sebanyak 2^30 untuk menghasilkan satu unit paparan. Hasil kedua menggunakan 2^-600 dengan zum 2^600; kedua-dua input dan hasil darabnya boleh diwakili untuk contoh ini. Magnitud kecil semata-mata tidak bermaksud kejituan hilang.

Program ini menggunakan a.addingProduct(b, c) untuk a + b × c. Apple mendokumenkan ini sebagai operasi tergabung tanpa pembundaran perantaraan. Ia mengurangkan pembundaran dalam operasi itu; penolakan setempat dan sebarang pembundaran input terdahulu kekal berasingan. addingProduct(::)

Bagaimanakah anda memilih belanjawan kejituan yang berguna?

Mulakan dengan jarak terkecil yang perlu kekal kelihatan pada zum semasa. Untuk sasaran ilustrasi sebanyak satu perempat unit paparan, bahagikan 0.25 dengan zum untuk mendapatkan belanjawan ralat dalam ruang adegan. Kemudian periksa jarak pada koordinat setempat dan nilai perantaraan yang digunakan dalam pengiraan.

Contohnya, pada zum 2^30, belanjawan ilustrasi ialah 2^-32 unit adegan. Pada koordinat setempat 512, jarak Double ialah 2^-43, jauh lebih halus daripada belanjawan itu. Pada koordinat mutlak 2^50, jaraknya ialah 0.25, terlalu kasar. Nilai terhitung ini menerangkan sebab sauh setempat yang berdekatan membantu paparan khusus ini.

Jarak ialah alat diagnostik, bukannya had ralat yang lengkap. Penukaran input, penolakan, transformasi dan penukaran lukisan akhir masing-masing boleh menyumbang kepada ralat. Uji anjakan yang diketahui melalui keseluruhan laluan, termasuk pemapar sebenar, sebelum menerima julat zum.

Pemerhatian Isu angka yang berkemungkinan Tindakan seterusnya
Penambahan ofset tidak mengubah koordinat yang disimpan Jarak penyimpanan melebihi kenaikan Kekalkan ofset secara berasingan atau pilih sauh yang lebih dekat
Koordinat relatif berfungsi sehingga penukaran Kejituan destinasi tidak mencukupi Tukar selepas penolakan asal dan zum; semak ralat yang terhasil
Nilai menjadi infiniti Operasi melebihi julat terhingga Ubah tertib pengiraan atau hadkan paparan yang disokong
Anjakan bukan sifar menjadi sifar Aliran bawah atau pembundaran menghapuskannya Skala semula lebih awal atau ubah perwakilan setempat
Sel yang jauh ditolak oleh program ini Dasar paparan terperinci telah dilampaui Pilih asal yang berdekatan atau gunakan pengiraan gambaran keseluruhan kasar yang berasingan

Untuk gambaran keseluruhan, sebahagian butiran kecil mungkin secara sah dipetakan kepada nilai yang jauh kurang daripada satu unit paparan. Tetapkan tingkah laku itu dengan jelas. Sekatan sel berjiran dalam contoh ini sengaja mengehadkan pengiraan terperinci; meluaskannya memerlukan belanjawan kejituan baharu, bukannya sekadar membuang pengadang.

Apakah yang berlaku pada skala yang sangat kecil dan semasa melukis?

Apple membezakan leastNormalMagnitude daripada nilai subnormal positif yang lebih kecil. Subnormal mempunyai kejituan yang berkurang berbanding nilai normal, jadi nombor positif terkecil bukan skala kerja lalai yang baik. Semak hasil perantaraan serta input apabila mendarabkan ofset kecil dengan zum. leastNormalMagnitude

Contoh ini menolak anjakan bukan sifar yang diskalakan menjadi sifar atau tidak lagi dapat dibezakan daripada asal skrin. Ia tidak mengesahkan setiap hasil bukan sifar terhadap toleransi visual. Tambahkan semakan penerimaan berdasarkan toleransi yang sesuai untuk kanvas anda, dan kendalikan ralat yang dilemparkan secara jelas dan bukannya menggantikan koordinat asal.

Untuk kanvas dua dimensi, kekalkan kedudukan terpisah bagi setiap paksi dan kira kedua-dua koordinat relatif sebelum melukis. Untuk peluasan tiga dimensi, Apple menyediakan simd_double3, alias bagi SIMD3<Double>. Setiap komponen kekal sebagai nilai titik apung 64-bit dengan had kejituannya sendiri. simd_double3

Kekalkan keadaan adegan terpisah sebagai kedudukan rujukan. Tukar hanya koordinat lukisan akhir yang berdekatan kepada jenis yang diperlukan oleh API lukisan anda, dan ukur ralat penukaran berbanding belanjawan ruang paparan. Jika nilai akhir itu masih sangat besar, tentukan dahulu sama ada objek tersebut berada dalam port pandangan semasa.

Kanvas yang stabil memerlukan julat sokongan yang ditetapkan: alamat sel integer, ofset setempat yang terhad, julat zum, dan ralat ruang paparan yang boleh diterima. Kekalkan butiran sebelum membentuk nilai besar, kira secara relatif kepada asal yang berdekatan, dan sahkan anjakan yang mewakili pada had julat tersebut.

Sumber