Swift'te hesaplanmış bir ızgara boyutunu tam sayı adedine dönüştürmek için önce yuvarlama kuralını seçin, ardından Int(exactly:) ile temsil edilebilirliği denetleyin. Döşemelerle kaplanan bir Mac tuvali için her ekseni yukarı yuvarlayın ve son çarpımda taşma olup olmadığını denetleyin.
Bu makale, bir masaüstü çizim aracı için bu hesabı geliştiriyor: Dikdörtgen biçimli bir tuval ve sabit boyutlu döşeler verildiğinde, tuvali kaplamak için gereken sütunları, satırları ve toplam döşeme sayısını belirleyin. Aşağıdaki tüm koordinatlar ve sayılar örnek amaçlıdır. Izgara, tuvalin kendi başlangıç noktasından başlar, boşluk içermez ve dış kenarlarda kısmi döşelere izin verir.
Yuvarlama kuralını ızgaranın amacına göre seçin
Kesirli bir ızgara boyutu, tür dönüşümünden önce bir politika gerektirir. Bir tuval 650 punto genişliğinde ve her döşeme 64 punto genişliğindeyse hesaplanan boyut 650.0 / 64.0, yani 10.15625 olur. Genişliğin tamamını kaplamak için 11 sütun gerekir. Yalnızca bu genişliğe sığan tam döşeleri sayarsanız sonuç 10 olur.
Swift, bu farklı işler için açık yuvarlama kuralları sunar. .up pozitif sonsuzluğa doğru, .down negatif sonsuzluğa doğru yuvarlar; .towardZero ise kesirli kısmı kaldırır. En yakına yuvarlama için eşitlik durumlarında uygulanacak ayrı kurallar vardır. Bu davranışlar Apple'ın FloatingPointRoundingRule belgelerinde tanımlanmıştır.
| Okuyucunun amacı | Negatif olmayan bir eksen boyutu için kural | 10.15625 için sonuç |
|---|---|---|
| Kenar döşemesi de dahil olmak üzere tüm alanı kaplamak | .up |
11.0 |
| Alana sığan tam döşeleri saymak | .down |
10.0 |
| En yakın tam boyutu seçmek | .toNearestOrAwayFromZero |
10.0 |
| Hesaplanan boyutun zaten tam sayı olmasını istemek | Yuvarlama yapmayın; doğrudan Int(exactly:) deneyin |
nil |
Alanı kaplama amacı için .rounded(.up) kullanın. Kural belirtmeden .rounded() çağrıldığında, en yakına ve eşitliklerde sıfırdan uzağa yuvarlama seçilir; bu da tuvalin bir bölümünü açıkta bırakabilir. Ayrıca, bir Double değerini yuvarlamak tam sayı değerli bir Double döndürür; tam sayıya dönüştürme ayrı bir adımdır. Apple'ın rounded(_:) belgelerine bakın.
Sütunları ve satırları ayrı ayrı yuvarlayın
Örnek amaçlı tuvalin yatay koordinatları 20 ile 670, dikey koordinatları ise 30 ile 440 arasındadır. Genişliği 650 punto, yüksekliği 410 puntodur. Bir kenarı 64 punto olan kare döşelerle alanı kaplama hesabı şöyledir:
- Sütunlar:
ceil(650 / 64) = 11. - Satırlar:
ceil(410 / 64) = 7. - Toplam:
11 × 7 = 77döşeme.
Bunlar, belirtilen düzenden elde edilen açıklayıcı aritmetik sonuçlardır. Tuval alanını döşeme alanına bölüp sonucu bir kez yuvarlamak 66 verir; bu sayı gereken dikdörtgen düzeni tanımlamaz. Her satırda 11 döşeme konumu, tuvalde ise bu satırlardan yedi tane gerekir. Her iki boyutu da yuvarlanana kadar ayrı tutun.
Bu formül, ilk döşemenin tuvalin başlangıç noktasında başladığını varsayar. Döşemeler başka bir noktaya sabitlenmiş bir ızgaraya aitse başlangıç ofseti, dikdörtgenin kesiştiği hücre sayısını değiştirebilir. Yalnızca genişliğe dayalı bir hesaplama yapmadan önce bu hizalamayı belirleyin.
Doğrudan tam sayı dönüşümü neden yeterli değildir?
Swift'in sıradan kayan nokta değerinden tam sayıya dönüştürücüsü sıfıra doğru yuvarlar. Bu nedenle Int(10.15625) 10 sonucunu verir. Kaynak değer, kesme işleminden sonra hedef tam sayı türüne de sığmalıdır; Apple, aralık dışındaki bir dönüşümün çalışma zamanı hatasına yol açabileceğini belirtiyor. BinaryInteger.init(_:) belgesine bakın.
Bu nedenle, hesaplanmış bir alanı kaplama boyutu için uygulanacak sıra şöyledir: Int(exactly: value.rounded(.up)), ardından isteğe bağlı değer işleme. Apple, Int.init(exactly:) yönteminin, verilen kayan nokta değeri tam sayı olarak tam biçimde temsil edilemiyorsa nil döndürdüğünü belgeliyor.
Bu başlatıcı dar kapsamlı bir soruyu yanıtlar: Verilen değer tam biçimde sığıyor mu? İlk geometri verisinin geçerli olduğunu, seçilen yuvarlama kuralının düzene uygun olduğunu veya bu sayıda döşeme oluşturmanın uygulanabilir olduğunu doğrulamaz. Aşağıdaki uygulamada bunlar ayrı denetimlerdir.
Sınırlandırılmış döşeme sayısı için eksiksiz bir Swift programı
Aşağıdaki program koordinatlar ve boyutlar için Double, sayımlar için Int kullanır. Sonlu ve negatif olmayan boyutlar, sonlu ve pozitif döşeme boyutları ve uygulama tarafından belirlenmiş pozitif bir döşeme sınırı gerektirir. Eksenlerden birinin boyutu sıfır olan tuval boş bir ızgara üretir. Ters sınırlar sessizce yeniden düzenlenmek yerine reddedilir.
Bu eksiksiz programı GridCount.swift olarak kaydedin ve kullanılabilir bir Swift araç zinciri bulunan Mac'te swift GridCount.swift komutuyla çalıştırın. Yalnızca Swift standart kitaplığını kullandığından içe aktarma gerekmez. Program beklenen sonuçlarla birlikte sunulmuştur; incelenmiş olsa da bu araştırma sırasında Swift derleyicisiyle çalıştırılmamıştır.
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)")
}
Denetimlerin sağladıkları
Sonlu değer denetimleri, NaN ve sonsuz değerlerin sayıma dönüşmesini engeller. Apple, isFinite özelliğinin her iki değer için de false olduğunu belirtir. Pozitif döşeme boyutları istemek, sıfıra bölmeyi ve negatif döşeme boyutlarını da engeller.
Girdileri doğruladıktan sonra bile bölüm sonucunu ayrıca denetlemek önemlidir. Uygulama sonlu olmayan bölüm sonuçlarını reddeder ve boyut pozitifken sonucun sıfır olmasını da reddeder. İkinci denetim, sıfıra taşan son derece küçük pozitif bir bölümün boş bir eksen olarak değerlendirilmesi yerine açıkça hatayla sonuçlanmasını sağlar. Bu, örneğin bilinçli olarak belirlenmiş bir sınırıdır.
Her eksenin başarıyla dönüştürülmesi, toplamın da denetlenmesi gerektiği gerçeğini değiştirmez. İki geçerli Int değerinin çarpımı, türün aralığının dışında kalabilir. Apple'ın multipliedReportingOverflow(by:) belgelerine göre partialValue, yalnızca overflow false olduğunda çarpımın tamamını içerir. Program, çarpımı kullanmadan önce bu bayrağı denetler.
Son maxCells karşılaştırması farklı bir amaca hizmet eder: Aritmetik sonucu sığsa bile uygulama sınırını uygular. Örnek olarak verilen 10.000 değeri, bellek veya çizim performansı için bir öneri değil, çağıranın seçtiği bir politikadır. Uygulamanızın her döşeme için gerçekte oluşturduğu öğelere göre bir sınır seçin.
Tam sayı sınırında kayan noktalı aralık denetiminden kaçının
Cazip görünen bir denetim, önce value <= Double(Int.max), ardından Int(value) kullanmaktır. Tam sayı sınırını kayan noktaya dönüştürmek değeri yuvarlayabileceğinden bu karşılaştırma yanıltıcı olabilir. Apple, Int'den Double.init(_:) yöntemini mümkün olan en yakın gösterime dönüştürme olarak açıkça tanımlar.
64 bitlik bir hedefte Int.max değeri 9_223_372_036_854_775_807'dir. Bunun Double dönüşümü 9_223_372_036_854_775_808 değerine yuvarlanır; bu, bir fazlasıdır. Bu yuvarlanmış sınıra eşit bir kayan nokta değeri karşılaştırmayı geçse de tam sayı aralığının dışında kalır. Apple, platform genişliği ile tam sayı boyutu arasındaki ilişkiyi Int başvuru sayfasında belgeliyor.
Bu sınır örneği, önerilen bir tuval boyutu değil, aritmetik bir açıklamadır. Programın temsil edilebilirliği doğrudan denetlemesi için Int(exactly:) yöntemini neden kullandığını açıklar. Ayrıca belirli bir tam sayı genişliğine bağlı azami sayıyı sabit kodlamaktan kaçınır.
Döşeme sınırına yakın değerleri nasıl ele alacağınıza karar verin
Yukarı yuvarlama, saklanan gerçek değere göre yapılır. İkiden biraz büyük bir bölüm, bu alanı kaplama politikası uyarınca üç döşeme gerektirir. Swift'teki nextUp özelliği, kayan nokta türünde bir sonraki temsil edilebilir büyük değeri sağlar; dolayısıyla (2.0).nextUp.rounded(.up), 3.0 değeridir. Bu bitişik değerin anlamı Apple'ın nextUp başvuru belgesinde açıklanmıştır.
Ekranda görünen bir değeri tam sayı gibi göstermek için rastgele bir epsilon çıkarmayın. Bu, alanı kaplama politikasını değiştirir ve küçük ama gerçek bir kalan için gereken döşemeyi ortadan kaldırabilir. Uygulama koordinatları bilerek ızgaraya oturtuyorsa bu kuralı yukarı akışta tanımlayın ve sayıları oturtulmuş geometri üzerinden hesaplayın. Boyutlar tam sayı birimlerden geliyorsa sayı hesabı boyunca bu birimleri kullanmayı düşünün.
Örnek ayrıca, sağlanan koordinatların çıkarılmasının kullanılabilir boyutlar verdiğini varsayar. Son dönüşümün tam olması, koordinat hesaplamaları sırasında daha önce kaybolmuş ayrıntıyı geri getiremez. Koordinat büyüklüklerini düzene uygun tutun ve tuval ile döşeme boyutlarında aynı birimleri kullanın.
Doğru davranışı ayırt eden durumları denetleyin
Özellikle yuvarlama politikasını veya boş ızgara davranışını değiştiriyorsanız uyarlamanızı doğrulamak için sınır durumlarını kullanın. Aşağıdaki beklenen sonuçlar, bu program için belirtilen sözleşmeden türetilmiştir; Swift ile çalıştırılmış testlerin sonucu olarak sunulmamıştır.
| Örnek girdi | Beklenen sonuç |
|---|---|
Tuval 650 × 410, döşemeler 64 × 64, sınır 10_000 |
11 sütun, 7 satır, 77 döşeme |
Tuval 128 × 128, döşemeler 64 × 64, sınır 10_000 |
2 sütun, 2 satır, 4 döşeme |
Tuval 129 × 128, döşemeler 64 × 64, sınır 10_000 |
3 sütun, 2 satır, 6 döşeme |
| Tuval eksenlerinden birinin boyutu sıfır; diğer girdiler geçerli | Toplamı sıfır olan boş ızgara |
| Negatif boyut, sıfır döşeme boyutu veya sonlu olmayan girdi | invalidInput |
| Pozitif boyutun döşeme boyutuna bölümü sıfır veya sonsuz | unrepresentableAxis |
Yukarı yuvarlanmış bir eksen Int türüne sığmıyor |
unrepresentableAxis |
| Geçerli eksen sayılarının çarpımı taşıyor | countOverflow |
77 döşemelik örnekte sınır 50 |
exceedsLimit |
Bu Mac tuvali için geometriyi her eksen hesaplanana kadar kayan nokta olarak tutun, kısmi kenar döşemelerini kapsamak için yukarı yuvarlayın ve Int(exactly:) üzerinden dönüştürün. Ardından ızgarayı oluşturmadan önce çarpımı ve uygulama sınırını doğrulayın. Bu sıra, hesaplanmış boyutları açıkça incelenebilir hata davranışına sahip bir sayıya dönüştürür.
Kaynaklar
- FloatingPointRoundingRule — Apple
- rounded(_:) — Apple
- BinaryInteger.init(_:) — Apple
- Int.init(exactly:) — Apple
- isFinite — Apple
- multipliedReportingOverflow(by:) — Apple
- Double.init(_:) — Apple
- Int — Apple
- nextUp — Apple