لماذا تقريب Double في Swift مؤلم (وكيف تتعايش معه في 2025)
كل مطور iOS يواجه عاجلاً أم آجلاً موقفاً: يحتاج لعرض سعر منتج للمستخدم 99.99، بينما في الكود 99.99000000000001. مألوف؟ مرحباً بك في عالم تقريب Double في Swift — الموضوع الذي جمع آلاف المشاهدات على Stack Overflow ولا يزال يشغل الأذهان حتى في 2025.
المشكلة ليست في Swift — المشكلة في طبيعة الأعداد ذات الفاصلة العائمة (IEEE 754). لا يمكن لـ Double تمثيل 0.1 بدقة، تماماً مثل 1/3 في النظام العشري. لكن بالنسبة للمستخدم، هذا ليس عذراً. إنه يحتاج أرقاماً جميلة على الشاشة.
في هذه المقالة سأعرض 5 طرق مجربة لتقريب Double في Swift، وسأشرح إيجابياتها وسلبياتها، وسأقدم خوارزمية واضحة للاختيار حسب مهمتك. هيا بنا!
الطريقة 1: Foundation — round() + pow(10, n) — الكلاسيكية
الطريقة الأكثر وضوحاً التي تتبادر إلى الذهن: ضرب الرقم في 10^n، تقريبه، ثم قسمته مرة أخرى.
import Foundation
func roundDouble(_ value: Double, toPlaces places: Int) -> Double { let divisor = pow(10.0, Double(places)) return round(value * divisor) / divisor}
let price = 99.995print(roundDouble(price, toPlaces: 2)) // 100.0 (تقريب مصرفي)الإيجابيات:
- البساطة وسهولة القراءة
- يعمل "مباشرة" — لا حاجة لاستيرادات إضافية
السلبيات:
- أخطاء الدقة — بسبب Double قد تحصل على 99.99000000000001 بدلاً من 99.99
- مشكلة التقريب المصرفي — round() تستخدم round half up، وهذا لا يناسب دائماً الأمور المالية
- pow(10, n) — عمليات حسابية زائدة
هذه الطريقة مناسبة للتقريب التقريبي في واجهة المستخدم، لكنها ليست للحسابات المالية.
الطريقة 2: String(format:) — التقريب عبر النص (الأسرع لواجهة المستخدم)
إذا كان هدفك النهائي هو عرض الرقم على الشاشة، فلماذا تتعامل مع Double أصلاً؟ حوله إلى نص فوراً.
let value = 123.456789let roundedString = String(format: "%.2f", value)print(roundedString) // "123.46"
// العودة إلى Double (إذا لزم الأمر)let roundedDouble = Double(roundedString) ?? 0.0الإيجابيات:
- سرعة فائقة — لا عمليات رياضية زائدة
- عدد مضمون وصحيح من المنازل في المخرجات
- دعم التوطين (فاصل عشري)
السلبيات:
- النتيجة نصية، وللحسابات اللاحقة تحتاج للتحويل مرة أخرى (فقدان دقة)
- غير مناسبة للتقريب المصرفي (round half up افتراضياً)
مثالية لعرض الأسعار، النسب المئوية، التقييمات — في كل مكان حيث الدقة ±0.01 غير حرجة.
الطريقة 3: NSDecimalNumber — ملك الدقة المالية
عندما تكون الأموال على المحك، Double هو الشر. استخدم NSDecimalNumber — فهو يخزن الأرقام بصيغة عشرية دون أخطاء تقريب.
import Foundation
func roundDecimal(_ value: Double, toPlaces places: Int, roundingMode: NSDecimalNumber.RoundingMode = .plain) -> Double { let decimal = NSDecimalNumber(value: value) let handler = NSDecimalNumberHandler( roundingMode: roundingMode, scale: Int16(places), raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false ) return decimal.rounding(accordingToBehavior: handler).doubleValue}
let amount = 99.995print(roundDecimal(amount, toPlaces: 2, roundingMode: .bankers)) // 99.99 (تقريب مصرفي!)الإيجابيات:
- تحكم كامل في التقريب — .plain, .down, .up, .b