Untuk mengubah ukuran grid hasil perhitungan menjadi jumlah bilangan bulat di Swift, tentukan aturan pembulatan terlebih dahulu, lalu gunakan Int(exactly:) untuk memeriksa apakah nilainya dapat direpresentasikan. Untuk kanvas Mac yang ditutupi ubin, bulatkan setiap sumbu ke atas dan periksa apakah perkalian akhirnya mengalami overflow.
Artikel ini menguraikan perhitungan tersebut untuk alat menggambar desktop: dengan kanvas persegi panjang dan ubin berukuran tetap, tentukan jumlah kolom, baris, dan total ubin yang diperlukan untuk menutupinya. Semua koordinat dan jumlah di bawah ini bersifat sintetis. Grid dimulai dari titik asal kanvas, tidak memiliki celah, dan mengizinkan ubin parsial di tepi luar.
Pilih aturan pembulatan berdasarkan tujuan grid
Dimensi grid pecahan memerlukan kebijakan sebelum dikonversi ke tipe lain. Jika lebar kanvas 650 poin dan lebar setiap ubin 64 poin, dimensi hasil perhitungannya adalah 650.0 / 64.0, atau 10.15625. Untuk menutupi seluruh lebar, diperlukan 11 kolom. Jika hanya menghitung ubin utuh yang muat dalam lebar tersebut, hasilnya 10.
Swift menyediakan aturan pembulatan eksplisit untuk berbagai kebutuhan ini. .up membulatkan ke arah tak terhingga positif, .down membulatkan ke arah tak terhingga negatif, dan .towardZero menghapus bagian pecahan. Pembulatan ke bilangan terdekat memiliki aturan terpisah untuk nilai yang tepat di tengah. Perilaku ini dijelaskan dalam dokumentasi FloatingPointRoundingRule dari Apple.
| Tujuan pembaca | Aturan untuk dimensi sumbu nonnegatif | Hasil untuk 10.15625 |
|---|---|---|
| Menutupi seluruh area, termasuk ubin tepi | .up |
11.0 |
| Menghitung ubin utuh yang muat di dalam area | .down |
10.0 |
| Memilih dimensi bilangan bulat terdekat | .toNearestOrAwayFromZero |
10.0 |
| Mengharuskan dimensi hasil perhitungan sudah berupa bilangan bulat | Tanpa pembulatan; coba Int(exactly:) secara langsung |
nil |
Untuk menutupi area, gunakan .rounded(.up). Memanggil .rounded() tanpa aturan akan memilih pembulatan ke bilangan terdekat dengan nilai tengah dibulatkan menjauhi nol, sehingga sebagian kanvas ini mungkin tidak tertutup. Selain itu, pembulatan Double menghasilkan Double bernilai bilangan bulat; konversi ke bilangan bulat tetap merupakan langkah terpisah. Lihat dokumentasi rounded(_:) dari Apple.
Bulatkan kolom dan baris secara terpisah
Misalkan kanvas sintetis membentang dari koordinat horizontal 20 hingga 670 dan vertikal 30 hingga 440. Lebarnya 650 poin dan tingginya 410 poin. Dengan ubin persegi yang masing-masing sisinya berukuran 64 poin, perhitungan cakupannya adalah:
- Kolom:
ceil(650 / 64) = 11. - Baris:
ceil(410 / 64) = 7. - Total:
11 × 7 = 77ubin.
Ini adalah hasil aritmetika ilustratif yang diturunkan dari tata letak yang disebutkan. Membagi luas kanvas dengan luas ubin lalu membulatkan sekali menghasilkan 66, yang tidak menggambarkan susunan persegi panjang yang diperlukan. Setiap baris memerlukan 11 posisi ubin, dan kanvas memerlukan tujuh baris seperti itu. Pertahankan kedua dimensi tersebut sampai keduanya selesai dibulatkan.
Rumus ini mengasumsikan ubin pertama dimulai dari titik asal kanvas. Jika ubin berada pada grid yang berpatokan ke titik lain, offset awal dapat mengubah jumlah sel yang beririsan dengan persegi panjang. Tetapkan perataan itu sebelum menerapkan perhitungan yang hanya menggunakan lebar.
Mengapa konversi langsung ke bilangan bulat tidak cukup
Inisialisasi floating-point ke bilangan bulat biasa di Swift membulatkan ke arah nol. Jadi, Int(10.15625) menghasilkan 10. Nilai sumber juga harus muat dalam tipe bilangan bulat tujuan setelah pemotongan; Apple mendokumentasikan bahwa konversi di luar rentang dapat menyebabkan kesalahan saat runtime. Lihat BinaryInteger.init(_:).
Karena itu, urutan yang tepat untuk dimensi cakupan hasil perhitungan adalah Int(exactly: value.rounded(.up)), lalu penanganan opsional. Apple mendokumentasikan bahwa Int.init(exactly:) mengembalikan nil jika nilai floating-point yang diberikan tidak dapat direpresentasikan secara tepat sebagai bilangan bulat.
Inisialisasi ini menjawab pertanyaan yang terbatas: apakah nilai yang diberikan ini dapat direpresentasikan secara tepat? Inisialisasi ini tidak memastikan bahwa geometri awalnya valid, aturan pembulatan yang dipilih sesuai untuk tata letak, atau pembuatan sejumlah ubin tersebut praktis. Semua itu memerlukan pemeriksaan terpisah dalam implementasi di bawah.
Program Swift lengkap untuk jumlah ubin yang dibatasi
Program berikut menggunakan Double untuk koordinat dan dimensi serta Int untuk jumlah. Program ini mensyaratkan rentang yang berhingga dan nonnegatif, dimensi ubin yang berhingga dan positif, serta batas ubin yang ditentukan aplikasi dengan nilai positif. Kanvas dengan salah satu rentang bernilai nol menghasilkan grid kosong. Batas yang terbalik ditolak, bukan diam-diam disusun ulang.
Simpan program lengkap ini sebagai GridCount.swift dan jalankan dengan swift GridCount.swift di Mac yang memiliki toolchain Swift. Program ini hanya menggunakan pustaka standar Swift, jadi tidak memerlukan impor. Program ditampilkan bersama hasil yang diharapkan; kodenya telah diperiksa, tetapi tidak dijalankan dengan kompiler Swift selama riset ini.
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)")
}
Fungsi pemeriksaan
Pemeriksaan nilai berhingga menolak NaN dan infinity sebelum keduanya dapat menjadi jumlah. Apple menyatakan bahwa isFinite bernilai false untuk keduanya. Mewajibkan dimensi ubin positif juga mengecualikan pembagian dengan nol dan ukuran ubin negatif.
Memeriksa hasil bagi secara terpisah tetap penting meskipun input sudah divalidasi. Implementasi menolak hasil bagi yang tidak berhingga dan menolak nilai nol jika rentangnya positif. Pemeriksaan terakhir ini membuat hasil bagi positif yang sangat kecil dan mengalami underflow menjadi nol dinyatakan sebagai kegagalan, bukan sebagai sumbu kosong. Ini merupakan batas yang disengaja dalam contoh ini.
Setelah konversi setiap sumbu berhasil, totalnya tetap perlu diperiksa. Dua nilai Int yang valid dapat menghasilkan perkalian di luar rentang tipe tersebut. Dokumentasi Apple tentang multipliedReportingOverflow(by:) menyatakan bahwa partialValue berisi hasil perkalian lengkap hanya jika overflow bernilai false. Program memeriksa flag tersebut sebelum menggunakan hasil perkalian.
Perbandingan maxCells terakhir memiliki tujuan berbeda: memberlakukan batas aplikasi meskipun perhitungannya muat. Nilai ilustratif 10.000 dipilih oleh pemanggil sebagai kebijakan, bukan rekomendasi untuk penggunaan memori atau performa menggambar. Pilih batas berdasarkan apa yang benar-benar dibuat aplikasi Anda untuk setiap ubin.
Hindari pemeriksaan rentang floating-point di batas bilangan bulat
Pemeriksaan yang menggoda adalah value <= Double(Int.max) diikuti Int(value). Perbandingan itu dapat menyesatkan karena mengonversi batas bilangan bulat ke floating-point dapat membulatkannya. Apple secara eksplisit menjelaskan Double.init(_:) dari Int sebagai konversi ke representasi terdekat yang mungkin.
Pada target 64-bit, Int.max bernilai 9_223_372_036_854_775_807. Konversi Double-nya dibulatkan menjadi 9_223_372_036_854_775_808, satu lebih besar. Nilai floating-point yang sama dengan batas yang telah dibulatkan itu akan lolos dari perbandingan meskipun berada di luar rentang bilangan bulat. Apple mendokumentasikan hubungan antara lebar platform dan ukuran bilangan bulat pada halaman referensi Int.
Contoh batas ini merupakan ilustrasi aritmetika, bukan usulan ukuran kanvas. Contoh ini menjelaskan alasan program meminta Int(exactly:) melakukan pemeriksaan keterwakilan secara langsung. Cara ini juga menghindari penetapan jumlah maksimum secara hard-code yang bergantung pada lebar bilangan bulat tertentu.
Tentukan cara menangani nilai di dekat batas ubin
Pembulatan ke atas mengikuti nilai yang benar-benar tersimpan. Hasil bagi yang sedikit lebih besar dari dua memerlukan tiga ubin menurut kebijakan cakupan ini. Properti nextUp di Swift menyediakan nilai floating-point berikutnya yang lebih besar dan dapat direpresentasikan, sehingga (2.0).nextUp.rounded(.up) adalah 3.0. Makna nilai yang berdekatan tersebut dijelaskan dalam referensi nextUp dari Apple.
Jangan mengurangi epsilon sembarang hanya agar nilai yang ditampilkan terlihat seperti bilangan bulat. Tindakan itu mengubah kebijakan cakupan dan dapat menghilangkan ubin yang diperlukan untuk sisa kecil yang nyata. Jika aplikasi sengaja menempelkan koordinat ke grid, tentukan aturan penempelan itu di tahap sebelumnya dan hitung jumlah berdasarkan geometri yang telah disesuaikan. Jika dimensi berasal dari satuan bilangan bulat yang eksak, pertimbangkan untuk mempertahankan satuan tersebut sepanjang perhitungan jumlah.
Contoh ini juga mengasumsikan bahwa pengurangan koordinat yang diberikan menghasilkan rentang yang berguna. Konversi akhir yang eksak tidak dapat memulihkan detail yang sudah hilang dalam aritmetika koordinat sebelumnya. Jaga besar koordinat agar sesuai dengan tata letak dan gunakan satuan yang sama untuk dimensi kanvas dan ubin.
Periksa kasus yang membedakan perilaku yang benar
Gunakan kasus batas untuk memverifikasi penyesuaian Anda, terutama jika Anda mengubah kebijakan pembulatan atau perilaku grid kosong. Hasil yang diharapkan berikut sesuai dengan kontrak yang dinyatakan program ini; hasil ini bukan laporan eksekusi pengujian Swift.
| Input sintetis | Hasil yang diharapkan |
|---|---|
Kanvas 650 × 410, ubin 64 × 64, batas 10_000 |
11 kolom, 7 baris, 77 ubin |
Kanvas 128 × 128, ubin 64 × 64, batas 10_000 |
2 kolom, 2 baris, 4 ubin |
Kanvas 129 × 128, ubin 64 × 64, batas 10_000 |
3 kolom, 2 baris, 6 ubin |
| Salah satu rentang kanvas bernilai nol; input lain valid | Grid kosong dengan total nol |
| Rentang negatif, dimensi ubin nol, atau input tidak berhingga | invalidInput |
| Rentang positif dibagi ukuran ubin menghasilkan nol atau infinity | unrepresentableAxis |
Sumbu yang dibulatkan ke atas tidak muat dalam Int |
unrepresentableAxis |
| Jumlah sumbu valid yang hasil perkaliannya overflow | countOverflow |
Contoh 77 ubin dengan batas 50 |
exceedsLimit |
Untuk tugas kanvas Mac ini, pertahankan geometri dalam floating-point sampai setiap sumbu selesai dihitung, bulatkan ke atas agar ubin tepi yang parsial ikut tercakup, lalu konversikan melalui Int(exactly:). Setelah itu, validasi perkalian dan batas aplikasi sebelum membuat grid. Urutan ini mengubah dimensi hasil perhitungan menjadi jumlah dengan perilaku kegagalan yang eksplisit dan mudah ditinjau.
Sumber
- FloatingPointRoundingRule — Apple
- rounded(_:) — Apple
- BinaryInteger.init(_:) — Apple
- Int.init(exactly:) — Apple
- isFinite — Apple
- multipliedReportingOverflow(by:) — Apple
- Double.init(_:) — Apple
- Int — Apple
- nextUp — Apple