الموضوع 71 من 91· هندسة البرمجيات والتسليم الرقمي

تجميع التكاليف الآمن للعملات

جمع كثير من البنود المالية — فواتير شهرية، تكاليف لكل موقع، أرقام أثر الميزانية متعددة السنوات — بالأعداد العشرية ذات الفاصلة العائمة الثنائية المعتادة (f64) يراكم أخطاء تمثيل صغيرة، لأن معظم الكسور العشرية (مثل 1,234.56$) لا يمكن تمثيلها تمثيلًا دقيقًا بالفاصلة العائمة الثنائية. كل خطأ صغير، لكن نموذجًا كبيرًا يجمع مئات أو آلاف البنود على مدى سنوات قد ينحرف بجزء من السنت — ويعتمد الانحراف على ترتيب الجمع، فلا يمكن تكراره. أما التجميع المالي المنجز بحساب عشري دقيق (أو بأعداد صحيحة لأصغر وحدة) فهو دقيق، متّسق مع ضرورة أن تتطابق الأنظمة المحاسبية والقيد المزدوج إلى آخر سنت.

لماذا يهم هذا

هذا نوع من أخطاء البرمجيات الموثَّق جيدًا والأساسي: ورقة Goldberg لعام 1991 في ACM Computing Surveys بعنوان "What Every Computer Scientist Should Know About Floating-Point Arithmetic" هي المرجع القياسي لسبب عدم قدرة الفاصلة العائمة الثنائية على تمثيل معظم المبالغ العشرية بدقة، ولماذا يضاعف جمع كثير منها الخطأ. ونماذج اقتصاد الصحة والمالية في NHS تجمع بانتظام سنوات متعددة وفئات تكلفة متعددة — فكلٌّ من التكلفة الإجمالية للملكية وتحليل أثر الميزانية يجمع كثيرًا من بنود التكلفة f64 على مدى سنوات. وحين يجب أن يتطابق النموذج إلى آخر سنت — تدقيق يحسب الإجمالي يدويًا يجب أن يصل إلى الرقم نفسه تمامًا — يجب أن يكون الحساب نفسه عشريًا دقيقًا لا فاصلة عائمة.

الحساب

التجميع الساذج:             الإجمالي = Σ f64(البند_i)       — انحراف يعتمد على الترتيب
التجميع الآمن للعملات:      الإجمالي = Σ Decimal(البند_i)   — دقيق وقابل للتكرار

تطبيق تعديل نسبي (مثل احتياطي):
  المعدَّل = الإجمالي × المعامل              — ناتج Decimal دقيق، قد يحمل منازل
                                              عشرية أكثر من أُسّ أصغر وحدة للعملة
  المقرَّب = قرِّب(المعدَّل, أُسّ_العملة, قاعدة_التقريب)  — يجب تحديد قاعدة
                                              التقريب صراحةً (half-up مقابل
                                              half-even/تقريب المصرفيين)

لاحظ الانضباط ذا الخطوتين: ضرب Decimal دقيق في معامل قد ينتج منازل عشرية أكثر مما تستخدمه العملة فعلًا (مثل ثلاث منازل من عدد بمنزلتين مضروب في معامل بمنزلتين) — وتلك الدقة الوسيطة لا تُقتطع تلقائيًا؛ فقط خطوة تقريب صريحة بقاعدة تقريب محددة تخفضها إلى أُسّ أصغر وحدة فعلية للعملة.

مثال محلول

اثنتا عشرة فاتورة شهرية متطابقة، قيمة كلٍّ منها 1,234.56$، تُجمع بحساب عشري دقيق: 1,234.56$ × 12 = 14,814.72$ بالضبط، مقابل جمع الثابت f64 ‏1234.56 اثنتي عشرة مرة بدقة IEEE-754 المضاعفة، وقد ينحرف بجزء من السنت بحسب ترتيب الجمع — نوع خطأ حقيقي وموثَّق، لا يمثل مشكلة لنموذج مبني على حساب Money عشري دقيق.

الآن طبّق احتياطي أثر ميزانية قياسيًا 5% (المعامل 1.05) على ذلك الإجمالي 14,814.72$: ‏14,814.72$ × 1.05 = 15,555.456$ — ثلاث منازل عشرية، لأن الضرب دقيق ولا يُقرَّب تلقائيًا إلى منزلتي العملة. وعند التقريب الصريح إلى منزلتين بتقريب المصرفيين (half-even) نحصل على 15,555.46$ بالضبط.

الصلة بهندسة البرمجيات

هذا هو الدرس الأساسي المباشر وراء مبدأ «البرمجيات المالية تستخدم Decimal لا float» — ويرتبط صراحةً بوحدتَي التكلفة الإجمالية للملكية وتحليل أثر الميزانية في هذا المستودع، اللتين تجمعان حاليًا تكاليف بالفاصلة العائمة العادية. لا تتطلب حجة الصحة ترحيل تلك النماذج فورًا؛ بل تحدد بدقة متى يجب أن يتطابق النظام إلى آخر سنت، ومن ثمّ لا يجوز له استخدام الفاصلة العائمة الثنائية لحسابه المالي. انظر التخصيص الدقيق للتكاليف إلى السنت للمسألة المقابلة وهي تقسيم (لا جمع) إجمالي دون فقدان سنت.

المزالق

  • التحويل إلى float في منتصف السلسلة: استخراج قيمة نقدية كعدد فاصلة عائمة أثناء الحساب (بعض مكتبات Money تسمي دالة التحويل هذه «lossy» تحذيرًا صريحًا) يُسقط بصمت ضمان الدقة لكل حساب بعد تلك النقطة.
  • «Decimal أبطأ من أن نهتم»: رفض الحساب العشري الدقيق بوصفه عبئًا غير ضروري، بينما تحتاج التقارير المالية إلى الصحة وقابلية التدقيق لا إلى الإنتاجية الخام.
  • تطبيق نسبة احتياطي دون تحديد قاعدة التقريب: half-up مقابل half-even (تقريب المصرفيين) قد يغيّر السنت الأخير؛ والاصطلاح نفسه يجب أن يكون خيارًا معلنًا وقابلًا للتدقيق — انظر تحليل التكلفة والفائدة لإرشادات الكتاب الأخضر (HM Treasury) بشأن تعديلات الاحتياطي وانحياز التفاؤل، وهي من نوع الأرقام التي تُطبَّق عليها خطوة التقريب هذه.

المصادر