حاسبة النسبة المئوية بالعربي: إزاي اتبنت، وليه الأرقام العربي أصعب مما تتخيل
قصة تقنية لبناء حاسبة النسبة المئوية في المدونة: قبول الأرقام العربي والفارسي والإنجليزي، والتقريب من غير أخطاء الكسور، وليه المعادلة «(٧٥ ÷ ٣٠٠) × ١٠٠» بتظهر بالمقلوب في الصفحات العربي وإزاي اتحلت.

محتويات المقال
حاسبة النسبة المئوية في المدونة شكلها بسيط: خانتين ونتيجة. بس بناء حاسبة بالعربي طلع فيه مشاكل ما بتظهرش في أي حاسبة إنجليزي. المقال ده عن المشاكل دي وإزاي اتحلت، لو بتبني أي حاجة فيها أرقام لجمهور عربي.
الهدف من الأول
- تحسب ٣ حاجات: كام في الميه من رقم، والرقم ده كام في الميه من رقم تاني، ونسبة الزيادة أو النقص.
- تقبل الأرقام زي ما الناس بتكتبها فعلًا.
- تعرض طريقة الحل مش النتيجة بس، عشان الناس تتعلم.
- الصفحة تبقى سريعة، وكل الشرح فيها يظهر لمحركات البحث.
المشكلة الأولى: الناس بتكتب الأرقام بكذا شكل
على الموبايل بالكيبورد العربي، الرقم بيتكتب ١٢٫٥. على الكمبيوتر بيتكتب 12.5. وفيه كيبوردات فارسي بتكتب ۱۲، وهي حروف مختلفة عن الأرقام العربي في الكمبيوتر حتى لو شكلها قريب. وناس بتكتب 1,000 أو ١٬٠٠٠ بفاصل الآلاف.
الحل كان دالة صغيرة «بتوحّد» أي مدخل قبل الحساب:
export function normalizeNumberInput(input: string): string {
return input
.replace(/[٠-٩]/g, (d) => String("٠١٢٣٤٥٦٧٨٩".indexOf(d)))
.replace(/[۰-۹]/g, (d) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(d)))
.replace(/٫/g, ".")
.replace(/[\s ٬,]/g, "")
.replace(/[−–]/g, "-");
}
لاحظ السطر الرابع: بيشيل المسافات، وكمان علامات الاتجاه الخفية (U+200E وU+200F وU+061C). العلامات دي ما بتظهرش، بس ممكن تتنسخ مع الرقم من أي نص عربي، وبتخلي الرقم «مش رقم» في نظر البرنامج.
وبعد التوحيد، الرقم بيتأكد إنه رقم فعلًا بتعبير منتظم، فـ 1e5 مثلًا بيترفض، رغم إن JavaScript بتعتبره رقم صحيح.
المشكلة التانية: التقريب
الكمبيوتر بيخزّن الكسور بطريقة ثنائية، فـ 1.005 × 100 في JavaScript بتطلع 100.49999999999999 مش 100.5. لو قرّبت مباشرة، النتيجة هتطلع غلط في حالات قليلة بس محرجة.
الحل: «زقّة» صغيرة جدًا قبل التقريب، تقرّب النص لبعيد عن الصفر:
const rounded = (Math.sign(n) * Math.round(Math.abs(n) * 100 * (1 + Number.EPSILON))) / 100;
وحالة تانية: لو النتيجة رقم صغير جدًا زي 0.003، التقريب لرقمين هيطلع ٠، وده مضلل لأن الإجابة مش صفر. فالحاسبة بتكتب «أقل من ٠٫٠١» بدل كده.
وفي العرض، النتيجة بتتكتب بالأرقام العربي بـ Intl.NumberFormat("ar-EG")، مع ملاحظة صغيرة: المكتبة بتحط علامة اتجاه خفية قبل علامة السالب، فبتتشال وتتحط علامة سالب عادية مكانها.
المشكلة التالتة، والأغرب: المعادلة بتظهر بالمقلوب
الحاسبة بتعرض الحل كده: (٧٥ ÷ ٣٠٠) × ١٠٠ = ٢٥٪. ولما المعادلة دي تتحط جوه صفحة عربي، المتوقع إنها تتعرض بترتيبها. بس اللي بيحصل إن الأقواس والعلامات بتتلخبط، والمعادلة بتطلع شكلها غلط.
السبب في خوارزمية الاتجاه (Unicode Bidirectional Algorithm) اللي المتصفح بيستخدمها عشان يرتب النص المختلط عربي وإنجليزي. الأرقام العربي (٠–٩) ليها تصنيف خاص في الخوارزمية دي، والعلامات اللي بينها (الأقواس و÷ و× و=) «محايدة» وبتاخد اتجاهها من اللي حواليها. فلما تحط المعادلة كلها جوه عنصر اتجاهه من الشمال لليمين، العلامات اللي بين الأرقام العربي بتفضل تتعامل على إنها من اليمين للشمال.
الحل اللي اشتغل: كل رقم يتعزل لوحده، والمعادلة كلها تتعزل كمان:
const NUMBER = /([0-9٠-٩][0-9٠-٩٫٬.,]*٪?)/;
export function MathExpr({ children }: { children: string }) {
const parts = children.split(NUMBER);
return (
<bdi dir="ltr" className="math">
{parts.map((part, i) => (i % 2 === 1 ? <bdi key={i} dir="ltr">{part}</bdi> : part))}
</bdi>
);
}
العنصر المعزول بيتعامل معاملة «علامة محايدة واحدة» من برّه، فالعلامات اللي بين الأرقام مبقتش بتشوف أرقام عربي جنبها، وبقت تاخد اتجاه المعادلة نفسها. والحلو إن الحل ده ما بيضيفش أي حرف للنص، فالنص المنسوخ وبيانات الأسئلة الشائعة لمحركات البحث بيفضلوا زي ما هم.
ونسخ النتيجة؟
زرار «نسخ النتيجة» بيحط النص في الحافظة، والحافظة ما فيهاش HTML ولا <bdi>. هنا الحل كان بعلامات الاتجاه الخفية نفسها اللي اتشالت من المدخلات: المعادلة كلها جوه علامتي عزل (LRI وPDI)، وكل رقم محاط بعلامة «شمال لليمين» (LRM)، فتتلزق صح في واتساب أو أي برنامج.
الصفحة نفسها
- الشرح كله بيتولد على السيرفر كـ HTML ثابت، والجزء الوحيد اللي بيشتغل بجافاسكريبت في المتصفح هو الحاسبة. يعني الصفحة سريعة، ومحركات البحث بتقرا كل الكلام.
- الأسئلة الشائعة متكتبة مرة واحدة وبتتعرض في الصفحة وفي بيانات الأسئلة الشائعة المنظمة لجوجل من نفس المصدر، فمستحيل الاتنين يختلفوا.
- كل منطق الحساب في ملف لوحده من غير React، وعليه اختبارات تلقائية بتتأكد من حاجات زي: الأرقام العربي والفارسي بتتقري صح، و
1e5بيترفض، والنتيجة عمرها ما تطلعNaNأوInfinity.
وبعدها: حاسبة المجموع
نفس الأساس اتستخدم في حاسبة النسبة المئوية للمجموع، بس فيها نقطة مهمة في الحساب نفسه: لما المواد درجاتها النهائية مختلفة، النسبة الصح هي مجموع الدرجات على مجموع النهايات، مش متوسط نسب المواد. مثلًا ٤٥ من ٥٠ (٩٠٪) و٣٠ من ٦٠ (٥٠٪): المتوسط ٧٠٪، لكن النسبة الحقيقية ٧٥ من ١١٠، يعني حوالي ٦٨٫١٨٪.
الدروس المستفادة
- وحّد المدخلات قبل أي حساب: الأرقام العربي، والفارسي، والفواصل، وعلامات الاتجاه الخفية.
- ما تثقش في التقريب المباشر للكسور في JavaScript.
- أي نص فيه أرقام عربي ورموز رياضية محتاج عزل اتجاه، والعزل لكل رقم هو اللي بيحلها فعلًا.
- النص اللي بيتنسخ محتاج معالجة غير النص اللي بيتعرض.
- افصل المنطق عن الواجهة عشان تقدر تختبره.
لو بتبني حاجة شبه كده، جرّب الحاسبة واكتب فيها أرقام بأشكال مختلفة، وشوف الحل بيتعرض إزاي.
مقالات ذات صلة
إيه الفرق بين HTML وCSS وJavaScript؟ شرح للمبتدئين بمثال واحد
أي موقع على النت مبني من تلات حاجات: HTML للمحتوى، وCSS للشكل، وJavaScript للحركة والتفاعل. اتعلم دور كل واحدة وإزاي بيشتغلوا مع بعض، من خلال زرار واحد بنبنيه خطوة بخطوة.
تبدأ تتعلم برمجة منين؟ خريطة واضحة للمبتدئين من غير تشتيت
تايه بين Python وJavaScript والكورسات اللي ملهاش آخر؟ خريطة عملية لأول ٦ شهور في تعلم البرمجة: تختار مجال، تتعلم الأساسيات، تبني مشاريع صغيرة، وتتجنب الفخاخ اللي بتوقّف المبتدئين.

