ملخص
علّمت الدروس الماضية من هذه الوحدة كتابة الحلقات والاستدلال عليها باللامتغير والمتغاير؛ وهذا الدرس يطبّق ذلك الاستدلال على أربع خوارزميات عددية كلاسيكية، أقدم من الحواسيب وما زالت مستعملة. اختبار الأولية بالقسمة التجريبية، الذي يتوقف عند لسبب يُبرهَن عليه، والذي يفيض شرط توقفه إذا كُتب بلا انتباه. وخوارزمية إقليدس للقاسم المشترك الأكبر، بلامتغيرها وتوقفها وفخّها مع الأعداد السالبة ومقارنتها بـstd::gcd. وتغيير الأساس بالقسمة المتتالية وقاعدة هورنر، التي تعيد بناء العدد من أرقامه وتحسب قيمة كثير الحدود بالمخطط نفسه. وطريقة التنصيف، التي تقرّب جذرًا بقسمة فترة إلى نصفين، وتضمنها مبرهنة القيمة الوسطى، وعدد جولاتها يُحسب مسبقًا. ويُعرض كل خوارزمية بوصفها كائنًا رياضيًا: ببرهان على أنها تفعل ما تقول، وبرهان على أنها تتوقف، وعدٍّ أوّلي لخطواتها.
أهداف التعلم
- تنفيذ اختبار الأولية بالقسمة حتى وتعليل لماذا يكفي الوصول إلى .
- تنفيذ خوارزمية إقليدس، وصياغة لامتغيرها (القاسم المشترك الأكبر لا يتغير) ومقارنتها بـ
std::gcd. - تحويل عدد إلى أساس آخر بالقسمة المتتالية، وحساب قيمة تمثيل موضعي، أو قيمة كثير حدود، بقاعدة هورنر.
- تقريب جذر بطريقة التنصيف حتى هامش تسامح معيّن، وحساب عدد الجولات التي يتطلبها ذلك.
القواسم والأعداد الأولية
القسمة التجريبية
يكون العدد الصحيح أوليًا إذا كان قاسماه الموجبان الوحيدان هما 1 و؛ وإذا كان له قاسم آخر فهو مؤلَّف؛ أما 0 و1 فليسا هذا ولا ذاك. ويوحي التعريف فورًا بخوارزمية: تجربة كل مرشَّح بين 2 و، والحكم على العدد بأنه مؤلَّف حالما يقسمه أحدها. وهي حلقة القاسم الأصغر من الدرس الحلقات، وهي صحيحة. غير أنها، مع عدد أولي من عشرة أرقام، تُجري قرابة عشرة مليارات قسمة، وكلها تقريبًا بلا فائدة، للسبب الآتي.
إذا كان مؤلَّفًا، فله قاسم يحقق .
البرهان
إذا كان مؤلَّفًا، فإنه يُكتب حاصلَ ضرب عاملين، لا يساوي أيٌّ منهما 1 ولا . ونسمّي أصغرَهما.
بضرب طرفي في العدد الموجب نحصل على ، ومن ثَمّ : العامل الأصغر في كل تحليل يقع على يسار .
فالقواسم تأتي إذن في أزواج متناظرة حول : مع ، الأزواج هي و و و. وإذا لم يقسم أيُّ مرشَّح حتى العددَ ، فلن يقسمه أيٌّ مما بعده، لأن قرينه كان سيظهر قبله.
ومن ذلك يلزم أن عدد القسمات ينخفض من نحو إلى نحو : فمع قريب من ملياري، ينخفض من ملياري قسمة إلى ستة وأربعين ألفًا. ويستعمل البرنامج نمط العَلَم من الدرس تصميم حلقات صحيحة: يبدأ isPrime صادقًا، وتُطفئه الحلقة إذا وجدت قاسمًا.
#include <iostream>#include <print>int main(){ int n{}; std::cin >> n; bool isPrime{n >= 2}; for (int d{2}; isPrime && d <= n / d; ++d) { if (n % d == 0) { isPrime = false; } } if (isPrime) { std::println("العدد {} أولي", n); } else { std::println("العدد {} ليس أوليًا", n); }}2147483647العدد 2147483647 أوليثلاثة قرارات تستحق التعليق. يُهيَّأ العَلَم بـn >= 2، فيُحكم على 0 و1 والأعداد السالبة بأنها غير أولية دون دخول الحلقة: ونسيان هذه الحالة هو الخطأ النموذجي، لأنه مع لا تُجري الحلقة أي جولة، فيحكم البرنامج بأن 1 أولي. وشرط for له جزءان: العَلَم، الذي يوقف الحلقة عند أول قاسم، والحدّ. والحدّ مكتوب d <= n / d، وهو، للأعداد الصحيحة الموجبة، مكافئ لـ (فالقسمة الصحيحة تبتر الكسر، لكن لا يتجاوز خارج القسمة المبتور بالضبط حين لا يتجاوز ، لأن عدد صحيح). والعدد المُدخل، ، هو أكبر قيمة من نوع int، وهو أولي؛ ويحسم البرنامج ذلك بـ باقيًا، هي بواقي قسمته على المرشَّحات من 2 إلى .
لامتغير الحلقة هو: لا يقسم أيُّ عدد صحيح من 2 إلى العددَ ، ما دام العَلَم صادقًا. وهو صحيح عند الدخول، حين تكون الفترة من 2 إلى 1 خالية؛ ويُحفظ، لأنه إذا لم يقسم العددَ فإن الفترة تزداد بعدد لا يقسمه؛ وعند الخروج والعَلَم صادق، يقول اللامتغير والشرط الكاذب، ، معًا إن أيَّ مرشَّح حتى لا يقسم ، وهذا ما تقتضيه القضية. والمتغاير هو عدد المرشَّحات الباقية للتجربة، أي عدد الأعداد الصحيحة بين و: ينقص واحدًا في كل جولة ولا ينزل تحت الصفر.
الشرط الذي يفيض
الطريقة الطبيعية لكتابة الحدّ هي d * d <= n، وهي خطأ. فمع المُدخل نفسه تصل الحلقة إلى ، وحينئذ يتجاوز حاصل الضرب القيمةَ العظمى لـint.
#include <iostream>#include <print>int main(){ int n{}; std::cin >> n; bool isPrime{n >= 2}; for (int d{2}; isPrime && d * d <= n; ++d) { if (n % d == 0) { isPrime = false; } } std::println("أولي؟ {}", isPrime);}2147483647main.cpp:9:33: runtime error: signed integer overflow: 46341 * 46341 cannot be represented in type 'int'انتهى البرنامج بالرمز 1.
يوقف كاشف السلوك غير المعرَّف البرنامج عند حاصل الضرب. ومن دونه، يكون فيضان الأعداد ذات الإشارة سلوكًا غير معرَّف، ولا يقول المعيار ماذا يحدث. وما يُلاحَظ مع GCC على Linux، مع التحسين ومن دونه، مفيد للتعلم: يلتفّ حاصل الضرب فيصير سالبًا أو أصغر من ، ولأنه لا توجد قيمة من نوع int تتجاوز القيمة العظمى، فإن الشرط d * d <= n لا يكفّ عن التحقق أبدًا؛ وتظل الحلقة تجرّب المرشَّحات نحو ملياري جولة، حتى تبلغ ، الذي يقسم ، فيحكم البرنامج بأن عددًا أوليًا مؤلَّف. فخطأ الفيضان لا يظهر في صورة فيضان، بل في صورة جواب خاطئ. أما الصورة d <= n / d فلا يمكن أن تفيض، لأنها تقسم بدل أن تضرب؛ وقد عرض الدرس البتات والمكتبة الرياضية مخرجًا آخر، هو حساب الحدّ مرة واحدة بـstd::sqrt، لكن دقة الحدّ تتوقف حينئذ على تقريب الفاصلة العائمة، والقسمة الصحيحة لا تعاني هذه المشكلة.
يبرهن الدرس «الأعداد الأولية والمبرهنة الأساسية في الحساب»، من دورة الجبر والهندسة 1، على أن كل عدد صحيح أكبر من 1 هو حاصل ضرب أعداد أولية بطريقة واحدة فقط؛ والقسمة التجريبية هي أقصر طريق لإيجاد هذا الحاصل، بقسمة على كل قاسم يُعثر عليه ومتابعة العمل بخارج القسمة.
جارٍ تحميل المحتوى…