السابقالفهرسالتالي

الفصل 7

التكرار

 

7.1 الإسناد المتعدد

لم أتكلم كثيراً عن ذلك سابقاً، لكن يسمح بعمل أكثر من عملية إسناد للمتغير الواحد في Java. إن ما ستفعله تعليمة الإسناد الثانية هو استبدال قيمة المتغير القديمة بواحدة جديدة.

int fred = 5;
System.out.print (fred);
fred = 7;
System.out.println (fred);
إن خرج هذا البرنامج هو 57، لأن أول مرة طبعنا فيها fred كانت قيمته 5، وفي المرة الثانية كانت قيمته 7.

هذا النوع من الإسناد المتعدد (multiple assignment) هو سبب الذي يدفعني لوصف المتغيرات بأنها حاويات للقيم. عندما تسند قيمة ما لتغير، فإنك تغير محتويات الحاوية، كما هو مبين في الشكل:

عند وجود عدة تعليمات إسناد لمتغير واحد، من المهم بصورة خاصة التمييز بين تعليمات الإسناد وتعليمات المساواة. بما أن Java تستعمل الرمز = للإسناد، فقد يؤدي ذلك لتفسير تعليمة مثل a = b على أنها تعليمة مساواة. وهي ليست كذلك!

أولاً، المساواة تبديلية، والإسناد ليس كذلك. مثلاً، في الرياضيات إذا كان a = 7 فإن 7 = a. لكن في Java التعليمة a = 7; هي تعليمة إسناد مشروعة، أما 7 = a; ليست كذلك.

أكثر من ذلك، في الرياضيات، تعليمة المساواة محققة دائماً. إذا كان a = b الآن، فإن a سيظل مساوياً لـ b إلى الأبد. في Java، يمكن لتعليمة الإسناد أن تساوي بين متغيرين، لكن لا شيء يفرض عليهما البقاء هكذا!

int a = 5;
int b = a; 		// a and b are now equal
a = 3; 			// a and b are no longer equal
يغير السطر الثالث قيمة a لكنه لا يغير قيمة b، وهكذا لم يعد a وb متساويين. في العديد من لغات البرمجة الأخرى يتم استعمال رمز آخر لعملية الإسناد، مثل -> أو =: ، وذلك لتفادي هذا الإرباك.

على الرغم من أن الإسناد المتعدد مفيد كثيراً، عليك التعامل معه بحذر. إذا استمرت قيم المتغيرات بالتغير في أجزاء البرنامج المختلفة، فستصبح الشفرة أصعب للقراءة والتنقيح.

7.2 التكرار

إن أحد الأشياء التي تستخدم الحواسيب لأجلها غالباً هو أتمتة العمليات التكرارية. تكرار المهام المتشابهة أو المتطابقة بدون عمل أخطاء هو شيء تبرع فيه الحواسيب ويفشل فيه البشر.

شاهدنا سابقاً برامج تنفذ عمليات مكررة تعاودياً، مثل countdown وfactorial، التي تستخدم التعاود لتكرار (repeat or iterate) التعليمات، كما توفر Java عدة مزايا تجعل كتابة الرامج التكرارية أسهل.

إن التعليمتين اللتين سنتعلمهما هما تعليمة while وتعليمة for.

7.3 تعليمة while

باستعمال تعليمة while، يمكننا إعادة كتابة countdown:

public static void countdown (int n) {
  while (n > 0) {
    System.out.println (n);
    n = n-1;
  }
  System.out.println ("Blastoff!");
}
يمكنك قراءة تعليمة while كما لو أنها مكتوبة باللغة الطبيعية تقريباً. هذه التعليمة تعني، "طالما n أكبر من الصفر، تابع طباعة قيمة n وبعدها إنقاص قيمة n بمقدار 1. عندما تصل للصفر، اطبع الكلمة "'Blastoff!'.

بصورة طبيعية أكثر، يكون مجرى تنفيذ تعليمة while كما يلي:

  1. احسب الشرط بين قوسين، يعطي true أو false.
  2. إذا كان الشرط غير محقق، اخرج من تعليمة while وتابع التنفيذ عند التعليمة التالية.
  3. إذا كان الشرط محققاً، نفذ التعليمات الموجودة بين الأقواس المنحنية، وبعدها ارجع إلى الخطوة 1.

هذا النوع من المسارات يدعى حلقة (loop) لأن الخطوة الثالثة تكون حلقة مع الخطوة الأولى. لاحظ أنه في حال عدم تحقق الشرط عند الوصول إلى الحلقة أول مرة، فلن يتم تنفيذ التعليمات الموجودة داخل الحلقة أبداً. تدعى التعليمات داخل الحلقة أحياناً جسم (body) الحلقة.

يجب أن يتم تغيير قيمة متغير أو أكثر ضمن جسم الحلقة بحيث، في النهاية، يصبح الشرط غير محقق وتنتهي الحلقة. وإلا فإن تكرار الحلقة سيستمر للأبد، وهو ما يدعى بالحلقة اللانهائية (infinite loop). من مصادر التسلية التي لا تنضب بالنسبة لعلماء الكمبيوتر هو ملاحظة أن التعليمات على علبة الشامبو، "ارغي، اشطف، كرر"، هي حلقة لانهائية.

في حالة countdown، يمكننا التأكد أن الحلقة ستنتهي لأننا نعلم أن قيمة n منتهية، ويمكننا أن نرى أن قيمة n تصغر في كل مرة يتم فيها تنفيذ الحلقة (في كل تكرار)، لذا يجب أن نصل إلى الصفر في النهاية. في حالات أخرى لا تكون معرفة ذلك سهلة:

public static void sequence (int n) {
  while (n != 1) {
    System.out.println (n);
    if (n%2 == 0) { 		// n is even
      n = n / 2;
    } else { 			// n is odd
      n = n*3 + 1;
    }
  }
}
إن شرط هذه الحلقة هو n != 1، لذا فإن تنفيذ الحلقة سيستمر حتى يصبح n مساوياً 1، ما سيجعل الشرط غير محقق.

في كل تكرار، سيطبع البرنامج قيمة n وبعدها يتحقق ما إذا كانت زوجية أو فردية. إذا كانت زوجية، سيتم تقسيمها على 2. إذا كانت فردية، سيتم استبدالها بالقيمة 3n + 1. مثلاً، إذا كانت القيمة الابتدائية (المتحول المرر إلى sequence) تساوي 3، سيكون التتابع الناتج هو 3، 10، 5، 16، 8، 4، 2، 1.

بما أن n يزداد أحياناً وينقص أحياناً أخرى، فلا يوجد أي إثبات واضح على أن n سيصل للواحد، أو أن البرنامج سينتهي. بالنسبة لقيم معينة من n، يمكننا إثبات أن البرنامج سينتهي. مثلاً، إذا كانت القيمة الابتدائية من قوى العدد 2، عندئذ ستكون قيمة n زوجية في كل مرة يتم تنفيذ الحلقة فيها، حتى نصل للواحد. المثال السابق ينتهي بسلسلة مثل هذه، تبدأ بالعدد 16.

إذا وضعنا القيم الخاصة جانباً، فإن السؤال المثير هو هل يمكننا إثبات أن هذا البرنامج سينتهي مع أية قيمة لـ n. حتى الآن، لم يتمكن أي شخص من إثبات ذلك أو إثبات العكس! لمزيد من المعلومات، انظر http://en.wikipedia.org/wiki/Collatz_conjecture.

7.4 الجداول

من الأشياء التي تفيدنا الحلقات فيها هو توليد وطباعة البيانات المجدولة. مثلاً، قبل توفر الحواسيب بسهولة، كان البشر مضطرين لحساب الخوارزميات، الجيب والتجيب (جيب التمام)، وتوابع رياضية شائعة أخرى يدوياً.

لتسهيل العملية، وجدت كتب تحوي جداول طويلة يمكنك أن تجد فيها قيم التوابع المختلفة. كان عملية إنشاء هذه الجداول طويلة ومملة، وغالباً ما كانت النتائج كلها أخطاء.

عندما دخلت الحواسيب على الساحة، كان أحد ردود الفعل المبدئية هو، "هذا عظيم! يمكننا استخدام الحواسيب لتوليد الجداول، وبذلك لن توجد فيها أي أخطاء". تبين فيما بعد أن ذلك كان صحيحاً (أغلب الأحيان)، لكنه قليل التبصر. فسرعان ما عم انتشار الحواسيب (والآلات الحاسبة) حتى أصبحت الجداول منتهية الصلاحية.

حسن، ليس تماماً. تبين فيما بعد أنه من أجل بعض العمليات، تستعمل الحواسيب جداول للقيم للحصول على أجوبة تقريبية، ثم تنفذ حسابات لتحسين نسبة التقريب. في بعض الحالات، وجدت أخطاء في الجداول التحتية، ومن أكثر تلك الحالات شهرة الخطأ في الجدول الذي استخدمه معالج إنتل بنتيوم الأول لتنفيذ عملية القسمة على الأعداد العشرية(1).

على الرغم من أن "جدول اللوغاريتم" لم يعد مفيداً كما كان، فلا يزال مثالاً جيداً على التكرار. البرنامج التالي يطبع سلسلة من القيم في العمود الأيسر ولوغاريتماتها في العمود الأيمن:

double x = 1.0;
while (x < 10.0) {
  System.out.println (x + "   " + Math.log(x));
  x = x + 1.0;
}
إن خرج هذا البرنامج هو:
1.0 	0.0
2.0 	0.6931471805599453
3.0 	1.0986122886681098
4.0 	1.3862943611198906
5.0 	1.6094379124341003
6.0 	1.791759469228055
7.0 	1.9459101490553132
8.0 	2.0794415416798357
9.0 	2.1972245773362196
بالنظر إلى هذه القيم، هل يمكنك معرفة الأساس الذي يستخدمه تابع log افتراضياً؟

نظراً لأن قوى العدد اثنين مهمة جداً في علوم الحاسوب، غالباً ما نرغب بحساب اللوغاريتمات ذات الأساس 2. لمعرفة هذه، يمكننا استخدام المعادلة التالية:

log2 x = loge x / loge 2      -----------------   (7.1)

بتغيير تعليمة الطباعة إلى

System.out.println (x + "   " + Math.log(x) / Math.log(2.0));
يعطي
1.0 	0.0
2.0 	1.0
3.0 	1.5849625007211563
4.0	2.0
5.0 	2.321928094887362
6.0 	2.584962500721156
7.0 	2.807354922057604
8.0 	3.0
9.0 	3.1699250014423126
يمكنك أن ترى أن 1، 2، 4 و8 هي قوى العدد 2، لأن لوغاريتماتها ذات الأساس 2 هي أعداد صحيحة. إذا أردت معرفة لوغاريتمات قوى العدد اثنين الأخرى، يمكننا تعديل البرنامج كما يلي:
double x = 1.0;
while (x < 100.0) {
  System.out.println (x + "   " + Math.log(x) / Math.log(2.0));
  x = x * 2.0;
}
الآن بدلاً من إضافة شيء ما إلى x في كل مرة تدور فيها الحلقة، ما يعطي متتالية حسابية، نضرب x بشيء ما، سيعطينا هذا متتالية هندسية. النتيجة هي:
1.0 	0.0
2.0 	1.0
4.0 	2.0
8.0 	3.0
16.0 	 4.0
32.0 	 5.0
64.0  	 6.0
قد لا تكون جداول اللوغاريتم مفيدة الآن، لكن بالنسبة لعالم كمبيوتر، فإن معرفة قوى 2 مهمة! في وقت ما عندما تملك دقيقة فراغ، عليك حفظ قوى الاثنين حتى 65536 (هذا يساوي 216).

7.5 الجداول ثنائية البعد

الجدول ثنائي البعد هو حيث نختار سطراً وعموداً ونقرأ القيمة عند تقاطعهما. جدول الضرب مثال جيد. لنقل أنك تريد طباعة جدول الضرب للأعداد من 1 إلى 6.

إحدى الطرق الجيدة للبدء هي كتابة حلقة بسيطة تطبع جدول ضرب 2، كله على سطر واحد.

int i = 1;
while (i <= 6) {
  System.out.print (2*i + " ");
  i = i + 1;
}
System.out.println ("");
يهيئ السطر الأول متغيراً اسمه i، وهو سيعمل كعداد، أو متغير الحلقة (loop variable). بينما يتم تنفيذ الحلقة، تزداد قيمة i من 1 إلى 6. وبعدها عندما تصبح قيمة i تساوي 7، تنتهي الحلقة. في كل دورة تكرارية للحلقة، نطبع القيمة i*2 متبوعة بثلاث مسافات. بما أننا نستعمل أمر print بدلاً من println، سيظهر الخرج كله على سطر واحد.

كما ذكرت في القسم 2.4، في بعض بيئات البرمجة يتم تخزين الخرج من تعليمة print بدون عرضها على الشاشة حتى يتم استدعاء println. إذا انتهى البرنامج، ونسيت أن تستدعي println، فقد لا ترى النتائج المخزنة أبداً.

إن خرج هذا البرنامج هو:

2	4	6	8	10	12
حتى الآن الوضع جيد. الخطوة التالية هي التغليف (encapsulation) والتعميم (generalization).

7.6 التغليف والتعميم

التغليف (أو الكبسلة) يعني عادة أخذ جزء من الشفرة ولفـُّه في عملية، ما يسمح لك بالاستفادة من مزايا كل الأشياء التي تفيدنا العمليات فيها. قد رأينا مثالين عن التغليف، عندما كتبنا printParity في القسم 4.3 وisSingleDigit في القسم 6.7.

التعميم يعني أخذ شيء مخصص، مثل طباعة جدول ضرب الاثنين، وتعميمه أكثر، مثل طباعة جدول الضرب لأي عدد صحيح.

إليك عملية تغلف الحلقة من القسم السابق وتعممها لتطبع جدول الضرب للعدد n.

public static void printMultiples (int n) {
  int i = 1;
  while (i <= 6) {
    System.out.print (n*i + " ");
    i  = i + 1;
  }
  System.out.println ("");
}
لتغليفها، كل ما كان علي فعله هو إضافة السطر الأول، الذي يصرح عن الاسم، المعامل، ونوع الإرجاع. للتعميم، كل ما كان علي فعله هو استبدال القيمة 2 بالمعامل n.

إذا استدعيت هذه العملية باستخدام المتحول 2، فسأحصل على نفس الخرج السابق. وباستخدام المتحول 3، سيكون الخرج كما يلي:

	3	6	9	12	15	18
مع المتحول 4، سيكون الخرج كما يلي
	4	8	12	16	20	24
ستتمكن على الأغلب من تخمين الطريقة التي سنطبع بها جدول الضرب الآن: سنستدعي printMultiples بشكل متكرر مع المتحولات المختلفة. في الواقع، سنستخدم حلقة أخرى لتكرر العملية على السطور.
int i = 1;
while (i <= 6) {
  printMultiples (i);
  i = i + 1;
}
قبل كل شيء، لاحظ الشبه بين هذه الحلقة وبين الحلقة الموجودة داخل printMultiples. كل ما فعلته هو استبدال تعليمة الطباعة باستدعاء لعملية.

خرج البرنامج سيكون كالتالي

1      2      3      4      5      6
2      4      6      8      10      12
3      6      9      12      15      18
4      8      12      16      20      24
5      10      15      20      25      30
6      12      18      24      30      36
وهو يمثل جدول الضرب (مع أنه مضطرب قليلاً). إذا أزعجك عدم ترتيب الجدول، فإن Java توفر عمليات تعطيك المزيد من التحكم بتنسيق الخرج، لكنني لن أخوض في الحديث عنها هنا.

7.7 العمليات

لقد ذكرت بعض فوائد العمليات في القسم 3.5. إليك بعض الأسباب الإضافية التي تجعل العمليات مفيدة:

7.8 المزيد من التغليف

لشرح التغليف مرة ثانية، سآخذ الشفرة من القسم السابق وأضعها في عملية:

public static void printMultTable () {
  int i = 1;
  while (i <= 6) {
    printMultiples (i);
    i  = i + 1;
  }
}
العملية التي أشرحها هي خطة تطوير شائعة تدعى التغليف والتعميم (encapsulation and generalization). تبدأ بإضافة أسطر إلى main أو أي عملية أخرى، وبعد ذلك عندما تعمل تلك الشفرة، تستخرجها وتغلفها بعملية. بعدها تعمم تلك العملية بإضافة معاملات.

أحياناً لا تعرف تماماً كيف ستقسم البرنامج إلى عمليات عندما تبدأ الكتابة. هذه الطريقة تسمح لك بتصميم البرنامج بينما تتقدم في طريقك.

7.9 المتغيرات المحلية

في هذا الوقت، قد تتساءل كيف تمكنا من استخدام نفس المتغير i في العمليتين printMultiples وprintMultTable. ألم أقل أنك تستطيع التصريح عن المتغير مرة واحدة فقط؟ وألا يسبب ذلك المشاكل عندما تبدّل إحدى العمليتين قيمة المتغير؟

إن الإجابة عن السؤالين هي "لا"، لأن i في العملية printMultiples وi في العملية printMultTable ليسا المتغير نفسه. صحيح انهما يملكان نفس الاسم، لكنهما لا يشيران إلى نفس المنطقة التخزينية، وإن تغيير قيمة أحدهما لا يؤثر على الآخر.

المتغيرات التي يتم التصريح عنها داخل تعريف عملية تدعى بالمتغيرات المحلية (local variables) لأنها محدودة بالعملية التي توجد فيها. لا يمكنك الوصول إلى متغير محلي من خارج عمليته "الأم"، ولك الحرية في امتلاك عدة متغيرات محلية بنفس الاسم، طالما أنها غير موجودة في العملية نفسها.

من الجيد غالباً استعمال أسماء مختلفة في العمليات المختلفة، لتفادي الإرباك، لكن توجد أسباب وجيهة لإعادة استعمال الأسماء. مثلاً، من الشائع استعمال الأسماء i، j، وk كمتغيرات حلقة. إذا تفاديت استعمالها في عملية واحدة فقط لأنها قد استعملت في مكان آخر، فعلى الأغلب أنك ستجعل البرنامج أصعب للقراءة.

7.10 المزيد من التعميم

كمثال آخر عن التعميم، تخيل أنك أردت برنامجاً يطبع جدول الضرب بأي قياس، ليس فقط جدول 6×6. يمكنك إضافة معامل إلى printMultTable:

public static void printMultTable (int high) {
  int i = 1;
  while (i <= high) {
    printMultiples (i);
    i = i + 1;
 }
}
استبدلت القيمة 6 بالمعامل high. إذا استدعيت printMultTable مع المتحول 7، سأحصل على
1 	2 	3 	4 	5 	6
2 	4 	6 	8 	10 	 12
3 	6 	9 	12 	 15 	  18
4 	8 	12 	 16 	  20 	   24
5 	10 	 15 	  20 	   25 	    30
6 	12 	 18 	  24 	   30 	    36
7 	14 	 21 	  28 	   35 	    42
وهو جدول جيد، عدا أنني أريده أن يكون مربعاً (له نفس العدد من الأسطر والأعمدة)، ما يعني إضافة معامل آخر إلى printMultiples، لتحديد عدد الأعمدة التي يجب أن يحتوي عليها الجدول.

فقط لأنني أريد أن أكون مزعجاً، سأدعو هذا المعامل high أيضاً، لأوضح أن العمليات المختلفة يمكن أن تملك معاملات لها نفس الاسم (تماماً مثل المتغيرات المحلية):

public static void printMultiples (int n, int high) {
  int i = 1;
  while (i <= high) {
    System.out.print (n*i + " ");
    i = i + 1;
  }
  newLine ();
}

public static void printMultTable (int high) {
  int i = 1;
  while (i <= high) {
    printMultiples (i, high);
    i = i + 1;
  }
}
لاحظ أنه عند إضافة المعامل الجديد، اضطررت إلى تغيير السطر الأول من العملية، كما اضطررت أيضاً إلى تغيير المكان الذي تم استدعاء العملية printMultiples فيه. كما هو متوقع، سيولد هذا البرنامج جدول 7×7 مربع:
1 	2 	3 	4 	5 	6 	7
2 	4 	6 	8 	10 	 12 	 14
3 	6 	9 	12 	 15 	  18 	  21
4 	8 	12 	 16 	  20 	   24 	   28
5 	10 	 15 	  20 	   25       30 	    35
6 	12 	 18 	  24 	   30       36 	    42
7 	14 	 21 	  28 	   35       42 	    49
عندما تعمم عملية ما بشكل مناسب، غالباً ما ستكتشف أن البرنامج الناتج يملك قدرات لم تكن تقصدها. مثلاً، لربما لاحظت أن جدول الضرب متناظر، لأن ab = ba، لذا فإن كل المدخلات في الجدول تظهر مرتين. يمكنك توفير الحبر بطباعة نصف الجدول فقط. لعمل ذلك، عليك تعديل سطر واحد فقط من العملية printMultTable. عدل
printMultiples (i, high);
إلى
printMultiples (i, i);
وستحصل على
1
2 	4
3 	6 	9
4 	8 	12 	 16
5 	10 	 15 	  20 	   25
6 	12 	 18 	  24 	   30     36
7 	14 	 21 	  28 	   35     42 	    49
سأترك لك مهمة اكتشاف كيفية حصول ذلك.

7.11 المصطلحات

حلقة: تعليمة يتم تنفيذها طالما أن الشرط محقق أو حتى يتحقق الشرط.

الحلقة اللانهائية: حلقة يكون شرطها محققاً دائماً.

الجسم: التعليمات الموجودة داخل الحلقة.

دورة (تكرارية): مرور واحد على (تنفيذ) جسم الحلقة، متضمناً التحقق من الشرط.

التغليف (الكبسلة): تقسيم برنامج كبير معقد إلى مكونات (مثل العمليات) وعزل المكونات عن بعضها البعض (مثلاً، باستخدام المتغيرات المحلية).

المتغير المحلي: متغير يتم التصريح عنه داخل عملية وهو يوجد ضمن حدود تلك العملية فقط. لا يمكن الوصول إلى المتغيرات المحلية من خارج عمليتها الأم، وهي لا تتعارض مع أي عملية أخرى.

تعميم: استبدال شيء غير مخصص بالضرورة (مثل قيمة ثابتة) بشيء عام بشكل مناسب (مثل متغير أو معامل). التعميم يجعل الشفرة متعددة الأغراض أكثر، وأكثر قابلية لإعادة الاستخدام، وفي بعض الأحيان تصبح الشفرة أسهل في الكتابة.

خطة التطوير: عملية تطوير البرنامج. في هذا الفصل، شرحت أسلوباً في التطوير مبني على أساس تطوير الشفرة للقيام بأعمال بسيطة ومحددة، ثم القيام بالتغليف والتعميم. في القسم 5.2 شرحت تقنية أسميتها التطوير التصاعدي. في الفصل الأخير سأقترح المزيد من أساليب التطوير.

loop: A statement that executes repeatedly while or until some condition is satisfied.

infinite loop: A loop whose condition is always true.

body: The statements inside the loop.

iteration: One pass through (execution of) the body of the loop, including the evaluation of the condition.

encapsulate: To divide a large complex program into components (like methods) and isolate the components from each other (for example, by using local variables).

local variable: A variable that is declared inside a method and that exists only within that method. Local variables cannot be accessed from outside their home method, and do not interfere with any other methods.

generalize: To replace something unnecessarily specific (like a constant value) with something appropriately general (like a variable or parameter). Generalization makes code more versatile, more likely to be reused, and sometimes even easier to write.

development plan: A process for developing a program. In this chapter, I demonstrated a style of development based on developing code to do simple, specific things, and then encapsulating and generalizing. In Section 5.2 I demonstrated a technique I called incremental development. In later chapters I will suggest other styles of development.

7.12 تمرينات

تمرين 7.1

public static void main (String[] args) {
  loop (10);
}

public static void loop (int n) {
  int i = n;
  while (i > 1) {
    System.out.println (i);
    if (i%2 == 0) {
      i = i/2;
    } else {
      i = i+1;
    }
  }
}
ارسم جدولاً يبين قيم المتغيرين i وn خلال تنفيذ الحلقة. يجب أن يحتوي الجدول على عمود واحد لكل متغير وسطر واحد لكل تكرار. ما هو خرج هذا البرنامج؟

تمرين 7.2

لنقل أنك أعطيت رقماً، a، وأنك تريد معرفة جذره التربيعي. إحدى الطرق هي تخمين الناتج، x0، ثم تحسين ذلك التخمين باستخدام المعادلة التالية:

x1 = (x0 + a/x0 ) /2      -----------------   (7.2)

مثلاً، إذا أردت معرفة الجذر التربيعي للعدد 9، وبدأت مع العدد x0 = 6، ثم x1 = (6+ 9/6)/2 = 15/4 = 3.75، وهو أقرب إلى الجواب الصحيح. يمكنك إعادة الإجراء نفسه، باستخدام x1 لحساب x2، وهكذا. في هذه الحالة، x2 = 3.075 و x3 = 3.00091. حيث يتقارب الرقم بسرعة إلى الإجابة الصحيحة (3). اكتب عملية باسم squareRoot تأخذ عدداً من نوع double كمعامل وتعيد الجذر التربيعي التقريبي لذلك المعامل، باستخدام هذه الخوارزمية. لا يمكنك استخدام العملية الجاهزة Math.sqrt. كما توقعتَ لأول وهلة، عليك استخدام a/2، يجب أن تتكرر عمليتك حتى تصل إلى عددين تقريبيين متتاليين يكون الاختلاف يبنهما أقل من 0.0001؛ بكلام آخر، حتى تصبح القيمة المطلقة لـ xn – xn-1 أقل من 0.0001. يمكنك استعمال العملية الجاهزة Math.abs لحساب القيمة المطلقة.

تمرين 7.3

في التمرين 6.9 كتبنا إصداراً تعاودياً من power، يأخذ عدداً عشرياً x وعدداً صحيحاً n ويعيد xn. الآن اكتب عملية تكرارية للقيام بنفس الحسبة.

تمرين 7.4

يقدم القسم 6.8 عملية تعاودية تحسب العاملي. اكتب إصداراً تكرارياً من factorial.

تمرين 7.5

إحدى طرق حساب ex هي استعمال السلسلة المنشورة اللانهائية

ex = 1 + x + x2/2! + x3/3! + x4/4! + ...      ---------   (7.3)

إذا كان اسم متغير الحلقة اسمه i، عندئذ يكون العنصر رقم i مساوياً xi /i!.

  1. اكتب عملية باسم myexp تجمع n من الحدود الأولى من السلسلة المبينة أعلاه. يمكنك استخدام عملية factorial من القسم 6.8 أو النسخة التكرارية التي كتبتها في التمرين السابق.
  2. يمكنك جعل هذه العملية أكثر فاعلية إذا أدركت أنه في كل تكرار يكون بسط الحد هو نفس بسط الحد السابق مضروباً بـ x والمقام هو نفس المقام السابق مضروباً بـ i. استفد من هذه الملاحظة للتخلص من العمليتين Math.pow وfactorial، بعدئذ تأكد من أن النتيجة لاتزال نفسها.
  3. اكتب عملية اسمها check تأخذ معامل وحيد، x، وتطبع قيم x، وMath.exp(x) وmyexp(x) لقيم مختلفة للمتغير x. يجب أن يبدو الخرج مشابهاً لما يلي:
    1.0	2.708333333333333	2.718281828459045
    
    مساعدة: يمكنك استخدام المحرف "t\" لطباعة علامة جدولة بين أعمدة الجدول.
  4. غير عدد الحدود في السلسلة (المتحول الثاني الذي ترسله check إلى myexp) وانظر إلى تأثير ذلك على دقة النتيجة. اضبط هذه القيمة حتى تتوافق القيمة التقريبية مع الإجابة "الصحيحة" عندما يكون x يساوي 1.
  5. اكتب حلقة في main تستدعي check مع القيمة 0.1، 1.0، 10.0 و100.0. كيف تتغير دقة النتيجة مع تغير قيمة x؟ قارن عدد الخانات المتوافقة بين القيم الحقيقية والمقدرة بدلاً من حساب الفرق بين القيمتين.
  6. أضف حلقة في main تتحقق من العملية check مع القيم -0.1، -1.0، -10.0، و -100.0. ضع تعليقاً على الدقة.

تمرين 7.6

من الطرق الممكنة لحساب هو استخدام المنشور اللانهائي التالي

بكلام آخر، نحتاج لجمع سلسلة من الحدود حيث يعطى الحد رقم i بالصيغة (-1)i x2i⁄i! . اكتب عملية باسم guess تأخذ x وn كمتحولات وتعيد مجموع n حد من الحدود الأولى من السلسلة. يجب عدم استخدام factorial أو pow.
(1) انظر http://en.wikipedia.org/wiki/Pentuim_FDIV_bug.
السابقالفهرسالتالي