لتحويل حجم شبكة محسوب إلى عدد صحيح يمثل العدد في Swift، اختر قاعدة التقريب أولًا، ثم استخدم Int(exactly:) للتحقق من قابلية التمثيل. بالنسبة إلى مساحة رسم على Mac تغطيها البلاطات، قرّب كل محور إلى الأعلى وتحقق من overflow في عملية الضرب النهائية.
تشرح هذه المقالة كيفية إجراء هذا الحساب في أداة رسم لسطح المكتب: عند إعطائك مساحة رسم مستطيلة وبلاطات ذات حجم ثابت، حدّد عدد الأعمدة والصفوف وإجمالي البلاطات اللازمة لتغطيتها. جميع الإحداثيات والأعداد أدناه افتراضية. تبدأ الشبكة عند أصل مساحة الرسم نفسها، ولا تتضمن فجوات، وتسمح ببلاطات جزئية عند الحواف الخارجية.
اختر قاعدة التقريب وفقًا لغرض الشبكة
يحتاج بُعد الشبكة الكسري إلى سياسة قبل تحويله إلى نوع آخر. إذا كان عرض مساحة الرسم 650 نقطة وعرض كل بلاطة 64 نقطة، فالبُعد المحسوب هو 650.0 / 64.0، أو 10.15625. تتطلب تغطية العرض بالكامل 11 عمودًا. أما احتساب البلاطات الكاملة التي تتسع ضمن هذا العرض فيعطي 10.
يوفر Swift قواعد تقريب صريحة لهذه المهام المختلفة. تقرّب .up نحو موجب ما لا نهاية، وتقرّب .down نحو سالب ما لا نهاية، بينما تحذف .towardZero الجزء الكسري. وللتقريب إلى أقرب عدد، توجد قواعد منفصلة لحالات التعادل. توضح وثائق Apple FloatingPointRoundingRule هذه السلوكيات.
| مهمة القارئ | القاعدة لبُعد محور غير سالب | النتيجة لـ 10.15625 |
|---|---|---|
| تغطية الامتداد بالكامل، بما في ذلك بلاطة الحافة | .up |
11.0 |
| احتساب البلاطات الكاملة التي تتسع داخل الامتداد | .down |
10.0 |
| اختيار أقرب بُعد صحيح | .toNearestOrAwayFromZero |
10.0 |
| اشتراط أن يكون البُعد المحسوب صحيحًا بالفعل | بلا تقريب؛ جرّب Int(exactly:) مباشرة |
nil |
لمهمة التغطية، استخدم .rounded(.up). إن استدعاء .rounded() من دون تحديد قاعدة يختار التقريب إلى أقرب عدد مع إبعاد حالات التعادل عن الصفر، وقد يترك جزءًا من مساحة الرسم هذه بلا تغطية. كذلك، فإن تقريب Double يعيد قيمة Double صحيحة القيمة؛ ويظل تحويلها إلى عدد صحيح خطوة منفصلة. راجع وثائق Apple الخاصة بـ rounded(_:).
قرّب الأعمدة والصفوف كلًّا على حدة
لنفترض أن مساحة الرسم الافتراضية تمتد من الإحداثي الأفقي 20 إلى 670، ومن الإحداثي العمودي 30 إلى 440. عرضها 650 نقطة وارتفاعها 410 نقاط. وباستخدام بلاطات مربعة طول ضلع كل منها 64 نقطة، يكون حساب التغطية كما يلي:
- الأعمدة:
ceil(650 / 64) = 11. - الصفوف:
ceil(410 / 64) = 7. - الإجمالي:
11 × 7 = 77بلاطة.
هذه نتائج حسابية توضيحية، مستنتجة من التخطيط المذكور. يؤدي تقسيم مساحة الرسم على مساحة البلاطة ثم التقريب مرة واحدة إلى 66، وهو عدد لا يصف الترتيب المستطيل المطلوب. يحتاج كل صف إلى 11 موضعًا للبلاطات، وتحتاج مساحة الرسم إلى سبعة صفوف من هذا النوع. احتفظ بالبُعدين إلى أن تقرّب كليهما.
تفترض هذه الصيغة أن البلاطة الأولى تبدأ عند أصل مساحة الرسم. إذا كانت البلاطات تنتمي بدلًا من ذلك إلى شبكة مثبتة عند موضع آخر، فقد يؤدي الإزاحة الأولية إلى تغيير عدد الخلايا التي يتقاطع معها المستطيل. حدّد هذا الاصطفاف قبل تطبيق حساب يعتمد على العرض وحده.
لماذا لا يكفي التحويل المباشر إلى عدد صحيح
تقرّب مُهيِّئة Swift المعتادة للتحويل من عدد ذي فاصلة عائمة إلى عدد صحيح نحو الصفر. لذلك، تنتج Int(10.15625) القيمة 10. ويجب أيضًا أن تلائم القيمة المصدر نوع العدد الصحيح الوجهة بعد حذف الجزء الكسري؛ وتوثّق Apple أن التحويل إلى قيمة خارج النطاق قد يؤدي إلى خطأ وقت التشغيل. راجع BinaryInteger.init(_:).
لذلك، يكون التسلسل المناسب لبُعد تغطية محسوب هو Int(exactly: value.rounded(.up))، متبوعًا بمعالجة اختيارية. توثّق Apple أن Int.init(exactly:) يعيد nil عندما يتعذر تمثيل قيمة الفاصلة العائمة المعطاة بدقة كعدد صحيح.
تجيب هذه المُهيِّئة عن سؤال محدد: هل تلائم هذه القيمة المعطاة النوع بدقة؟ لكنها لا تثبت أن الهندسة الأصلية صالحة، أو أن قاعدة التقريب المختارة تناسب التخطيط، أو أن إنشاء هذا العدد من البلاطات عملي. هذه فحوص منفصلة في التنفيذ أدناه.
برنامج Swift كامل لعدد محدود من البلاطات
يستخدم البرنامج التالي Double للإحداثيات والأبعاد وInt للأعداد. ويشترط امتدادات منتهية وغير سالبة، وأبعاد بلاطات منتهية وموجبة، وحدًا موجبًا للبلاطات تحدده حسب التطبيق. إذا كان أي من امتدادي مساحة الرسم يساوي صفرًا، فستنتج شبكة فارغة. وتُرفض الحدود المعكوسة بدلًا من إعادة ترتيبها بصمت.
احفظ هذا البرنامج كاملًا باسم GridCount.swift وشغّله بالأمر swift GridCount.swift على جهاز Mac يتوفر عليه نظام أدوات Swift. لا يستخدم البرنامج سوى مكتبة Swift القياسية، لذلك لا يتطلب أي استيرادات. عُرض البرنامج مع النتائج المتوقعة؛ وقد تمت مراجعته، لكنه لم يُشغّل باستخدام مترجم Swift أثناء هذا البحث.
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)")
}
ما الذي تحققه هذه الفحوص
ترفض فحوص القيم المنتهية قيم NaN واللانهاية قبل أن تتحول أي منهما إلى عدد. وتوضح Apple أن isFinite تكون قيمته false لكليهما. كما أن اشتراط أبعاد بلاطات موجبة يستبعد القسمة على صفر وأحجام البلاطات السالبة.
من المهم فحص ناتج القسمة أيضًا، حتى بعد التحقق من المدخلات. يرفض التنفيذ ناتج القسمة غير المنتهي، ويرفض الصفر عندما يكون الامتداد موجبًا. ويجعل هذا الفحص الأخير ناتج القسمة الموجب الصغير جدًا الذي يفيض إلى الصفر حالة فشل صريحة بدلًا من أن ينتج محورًا فارغًا. وهذا حد مقصود في المثال.
يبقى التحقق من الإجمالي ضروريًا بعد نجاح تحويل كل محور. فقد يكون حاصل ضرب قيمتي Int صالحتين خارج نطاق النوع. توضح وثائق Apple الخاصة بـ multipliedReportingOverflow(by:) أن partialValue يحتوي على حاصل الضرب الكامل فقط عندما تكون overflow false. ويتحقق البرنامج من هذا العلم قبل استخدام حاصل الضرب.
تؤدي مقارنة maxCells الأخيرة غرضًا مختلفًا: فهي تفرض حدًا خاصًا بالتطبيق حتى عندما تكون العملية الحسابية ضمن النطاق. والقيمة التوضيحية 10,000 سياسة يحددها المستدعي، وليست توصية بشأن الذاكرة أو أداء الرسم. اختر حدًا بناءً على ما ينشئه تطبيقك فعلًا لكل بلاطة.
تجنب فحص نطاق بالفاصلة العائمة عند حد الأعداد الصحيحة
من المغري إجراء فحص value <= Double(Int.max) ثم Int(value). لكن هذه المقارنة قد تكون مضللة لأن تحويل حد العدد الصحيح إلى فاصلة عائمة قد يقرّبه. وتصف Apple صراحةً Double.init(_:) من Int بأنه تحويل إلى أقرب تمثيل ممكن.
في هدف 64 بت، تكون قيمة Int.max هي 9_223_372_036_854_775_807. ويقرّب تحويلها إلى Double القيمة إلى 9_223_372_036_854_775_808، أي أكبر بواحد. ستجتاز قيمة فاصلة عائمة تساوي هذا الحد المقرّب المقارنة، مع أنها تظل خارج نطاق العدد الصحيح. توثّق Apple العلاقة بين عرض المنصة وحجم العدد الصحيح في الصفحة المرجعية لـ Int.
هذا المثال الحدّي توضيح حسابي، وليس اقتراحًا لحجم مساحة رسم. وهو يشرح سبب طلب البرنامج من Int(exactly:) إجراء فحص قابلية التمثيل مباشرةً. كما يتجنب تثبيت حد أقصى للعدد مرتبط بعرض نوع العدد الصحيح في منصة معينة.
قرّر كيفية التعامل مع القيم القريبة من حد البلاطة
يتبع التقريب إلى الأعلى القيمة المخزّنة فعليًا. فإذا كان ناتج القسمة أكبر بقليل من اثنين، فسيحتاج هذا النهج الخاص بالتغطية إلى ثلاث بلاطات. توفر خاصية Swift nextUp قيمة الفاصلة العائمة التالية الأكبر القابلة للتمثيل، لذا فإن (2.0).nextUp.rounded(.up) تساوي 3.0. توثّق Apple معنى هذه القيمة المجاورة في مرجع nextUp.
لا تطرح قيمة epsilon اعتباطية لمجرد جعل القيمة المعروضة تبدو صحيحة. فهذا يغيّر سياسة التغطية وقد يحذف بلاطة لازمة لبقية حقيقية صغيرة. إذا كان التطبيق يقرّب الإحداثيات عمدًا إلى شبكة، فحدّد قاعدة التقريب تلك في خطوة سابقة واحسب الأعداد انطلاقًا من الهندسة المقرّبة. وإذا كانت الأبعاد ناتجة عن وحدات صحيحة دقيقة، ففكّر في الاحتفاظ بهذه الوحدات خلال حساب العدد.
يفترض المثال أيضًا أن طرح الإحداثيات المعطاة يعطي امتدادات مفيدة. ولا يمكن لتحويل نهائي دقيق استعادة تفاصيل فُقدت بالفعل في عمليات حساب الإحداثيات السابقة. اجعل مقادير الإحداثيات مناسبة للتخطيط واستخدم الوحدات نفسها لمساحة الرسم وأبعاد البلاطات.
افحص الحالات التي تميّز السلوك الصحيح
استخدم حالات حدّية للتحقق من التعديلات التي تجريها، وخصوصًا إذا غيّرت سياسة التقريب أو سلوك الشبكة الفارغة. النتائج المتوقعة التالية متوافقة مع العقد المعلن لهذا البرنامج؛ وليست نتائج اختبارات Swift أُجريت فعليًا.
| المدخل الافتراضي | النتيجة المتوقعة |
|---|---|
مساحة رسم 650 × 410، بلاطات 64 × 64، الحد 10_000 |
11 عمودًا، و7 صفوف، و77 بلاطة |
مساحة رسم 128 × 128، بلاطات 64 × 64، الحد 10_000 |
عمودان، وصفّان، و4 بلاطات |
مساحة رسم 129 × 128، بلاطات 64 × 64، الحد 10_000 |
3 أعمدة، وصفّان، و6 بلاطات |
| أي امتداد من مساحة الرسم يساوي صفرًا؛ والمدخلات الأخرى صالحة | شبكة فارغة بإجمالي صفر |
| امتداد سالب، أو بُعد بلاطة يساوي صفرًا، أو مدخل غير منتهٍ | invalidInput |
| امتداد موجب ينتج عنه ناتج قسمة يساوي صفرًا أو اللانهاية عند قسمته على بُعد بلاطة | unrepresentableAxis |
تعذّر احتواء محور بعد تقريبه إلى الأعلى ضمن Int |
unrepresentableAxis |
| أعداد محاور صالحة، لكن حاصل ضربها يفيض | countOverflow |
مثال البلاطات الـ77 مع حد 50 |
exceedsLimit |
في مهمة مساحة الرسم على Mac هذه، أبقِ الهندسة بأعداد الفاصلة العائمة إلى أن تحسب كل محور، وقرّب إلى الأعلى لتضمين بلاطات الحواف الجزئية، ثم حوّل باستخدام Int(exactly:). بعد ذلك، تحقّق من عملية الضرب ومن حد التطبيق قبل إنشاء الشبكة. يحوّل هذا التسلسل الأبعاد المحسوبة إلى عدد مع سلوك واضح للفشل يسهل مراجعته.
المصادر
- FloatingPointRoundingRule — Apple
- rounded(_:) — Apple
- BinaryInteger.init(_:) — Apple
- Int.init(exactly:) — Apple
- isFinite — Apple
- multipliedReportingOverflow(by:) — Apple
- Double.init(_:) — Apple
- Int — Apple
- nextUp — Apple