الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

استخدام الإستدعاء الذاتي في قانون تايلور

بدأه khaled_prg في 30 يوليو 2010 · 14 رد · 1,277 مشاهدة · في الأسئلة المجابة
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

السلام عليكم

المطلوب هو كتابة كود يحسب sin لزاوية معينة يُدخلها المستخدم و ذلك بالإعتماد على قانون تايلور (انظر االصورة).

07rjlsx30.jpg

يمكننا فعل ذلك عن طريق كتابة 3 دوال كالتالي:

int fact(int x) 
{ 
        int result = 1; 

        for(int i=2 ; i<=x ; i++) 
                result *= i; 

        return result; 
} 

double pow(double x,double y) 
{ 
        double result=x; 

        for(int i=1 ; i<y ; i++) 
                result *= result; 

        return result; 
} 

float sin(float x) 
{ 
        float result = x; 

        int limit = 10; 
        int sign = -1; 

        for(int i=3 ; i<limit ; i+=2,sign=-sign) 
                result += sign*pow(x,i)/fact(i); 

        return result; 
}

و لكنني أريد جمع الدوال الثلاثة في دالة واحدة كالآتي:

float sin(float x,int r)
{
  int i=0,n=0;
  float result=0;
  if(r%2==0) r++;
  while(n!=r)
  {
    if(x==0) result=1;
     else
     {
       if(++i%2!=0)
         result+=(x*sin(x,n-1)/n*sin(1,n-1));
        else
         result+=(-x*sin(x,n-1)/n*sin(1,n-1));
      }
       n+=2*n+1;
   }
  return result;
}

الفكرة ببساطة هي محاولة اختزال الدالتين fact و pow في دالة واحدة.

الدالة sin تستقبل متغيرين أحدهما float و الآخر int

بالنسبة لـ x فهي الزاوية المُراد حساب جيبها و المتغير r عبارة عن الأس الذي نتوقف عنده.

الهدف من المتغير i هو تغيير إشارة الجزء المُضاف للمجموع في كل مرة فإذا كان i فرديا فإننا نضع إشارة موجبة أمام المجموع أما إذا كان i زوجيا فإننا نضع إشارة سالبة أمام المجموع.

أما المتغير n فهو يمثل الأس في كل مرة و السبب في كتابة هذا السطر:

n+=2*n+1;

لأن الأس فردي دائما , لاحظ أننا نتوقف عندما تكون n تساوي r أي أن المتغير n يمثل الأس المؤقت.

لاحظ أننا نزيد المتغير r بواحد عندما يكوت زوجيا و ذلك لكي لا تكون الدوارة غير منتهية.

-------------------

حاولت أن أضع الدالة في كود هكذا:

#include<iostream>
using namespace std;
float sin(float x,int r)
{
  int i=0,n=0;
  float result=0;
  if(r%2==0) r++;
  while(n!=r)
  {
    if(x==0) result=1;
     else
     {
       if(++i%2!=0)
         result+=(x*sin(x,n-1)/n*sin(1,n-1));
        else
         result+=(-x*sin(x,n-1)/n*sin(1,n-1));
      }
       n+=2*n+1;
   }
  return result;
}
int main()
{
    cout<<sin(2,7)<<endl;
    system("pause");
    return 0;
}

انتظر تعليقاتكم على الدالة .. أريد أن أضعها في كود و يعمل :)

تحياتي.

#2

يمكنك الإستغناء عن الداله fact و الداله pow و ايضا جعل الداله sin الناتجه اسرع كثيرا جدا مما كانت عليه، انظر الكود التالي يليه الشرح:

#define PI     3.1415926535897932384626433832f // pi
#define PI_1_2 1.5707963267948966192313216916f // pi / 2
#define PI_2   6.2831853071795864769252867664f // 2 * pi

float sin2(float r)
{
	while(r>=PI_2) r-=PI_2;

	float sum = r;
	float r_pow = r;
	int sign = 1;

	if (r>PI)
	{
		sign = -1;
		r -= PI;
		sum = r_pow = r;
	}

	float fact[] = {
		-0.1666666666666666666666666667f, // -1/3!
		 0.0083333333333333333333333333f, // +1/5!
		-0.0001984126984126984126984127f, // -1/7!
		 0.0000027557319223985890652557f, // +1/9!
		-0.0000000250521083854417187751f, // -1/11!
		 0.000000000160590438368216146f , // +1/13!
		-0.0000000000007647163731819816f, // -1/15!
		 0.0000000000000028114572543455f  // +1/17!
	};


	for(int i=0; i<8; i++)
	{
		r_pow *= r * r;
		sum += r_pow * fact;
	}

	return sign*sum;
}

inline float cos2(float r)
{ return sin2(PI_1_2 - r); }

inline float tan2(float r)
{ return sin2(r) / cos2(r); }

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

هذه الطريقه تتيح لك سرعه عاليه لعدة اسباب منها عمليات حسابيه اقل و ايضا لا يوجد قسمه حيث انها ابطأ من الضرب و ايضا لا يوجد ارقام بأحجام كبيره حيث اننا قمنا بحساب هذه الأرقام و كتبناها بشكل مباشر داخل الكود، ايضا توجد اسباب اخرى تجعل من هذا الكود اسرع. المصفوفه fact تحتوى على مضروب الأرقام من 3 و 17.

دالة الأس نحن نحتاجها لحساب الزاويه مرفوعه للرقم 3 و 5 و 7 و 9 .... و كما ترى فإن الزاويه مرفوعه للأس 5 تعنى الزاويه مرفوعه للأس 3 ثم نضربها فى نفسها مرتين و هذا يعتبر optimization حيث انك لن تحتاج لإجراء عملية ضرب الزاويه فى نفسها بدئا من 1 و صولا للأس الذى تريده و لكنك كل ما ستفعله هو اخذ قيمة اخر قيمه حصلت عليه للزاويه مرفوعه للأس ثم تضرب هذا الرقم فى الزاويه مرتينu0648و هذا هو وظيفة المتغير r_pow حيث يحتفظ بقيمة اخر power.

كما تعلم فإن دائرة الوحده للداله sin يعتبر فيها الربع الأول و الثانى (من الزاويه صفر و حتى 179) هى قيمه موجبه و الربع الثالث و الرابع (من الزاويه 180 و حتى 359) يماثل الربع الأول و الثانى و لكن بإشارة سالبه، بمعنى الزاويه 300 تساوى في قيمتها الزاويه 120 (300 - 180 = 120) و حيث ان الزاويه 330 تقع فى الربع الرابع لذا هى سالبه و بالتالى إذا كانت الزاويه لدينا اكبر من 180 حينها نستطيع حذف 180 درجه من الزاويه و الناتج نضربه فى -1 و ستكون النتيجه مماثله و هذا ما يحدث بالكود بالأعلى.

و الله ولي التوفيق

تم تعديل هذه المشاركة بواسطة محمد علاء الدين في 31 يوليو 2010 في 14:53

1

مدونتي: C++ Tips and Tricks

#3

السلام عليكم

+1 على الكود الجميل و السريع أيضا :)

-------------------------------

و لكن مالعيوب الموجودة في الدالة التي كتبتها !؟ ولماذا لا يعمل الكود !؟

بالتوفيق.

#4

السلام عليكم

اقتباس
صراحة لا استطيع فهم دالة sin ألتى كتبتها

يمكن لأن شرحي للدالة غير مفصل ! و لكن ما هو الشيء الذي لم تفهمه بالضبط !؟

#8
اقتباس
هل يوجد غموض في الدالة التي كتبتها ...!

أين الغموض بالتحديد !؟

بالتأكيد يوجد، هذه الداله مشفره :lol: و تحتاج لداله لفك تشفيرها، من الصعب مساعدتك فى فك تشفيرها حيث انك من كتبتها لذا على الأقل قم بكتابة تعليق علي بعض من سطورها او اشرح بشكل مفصل الأكواد بالترتيب.

و الله ولي التوفيق

مدونتي: C++ Tips and Tricks

#9

السلام عليكم

اقتباس
بالتأكيد يوجد، هذه الداله مشفره :lol: و تحتاج لداله لفك تشفيرها، من الصعب مساعدتك فى فك تشفيرها حيث انك من كتبتها لذا على الأقل قم بكتابة تعليق علي بعض من سطورها او اشرح بشكل مفصل الأكواد بالترتيب.

طيب .. سأحاول فك تشفير الدالة ...! :lol: و إن شاء الله يكون الشرح وافي :wink:

---------------------------------------

كلما أريده هو اختزال الدوال الثلاثة (الموجودة في المشاركة الأولى) في دالة واحدة كالآتي:

float sin(float x,int r)
{
  int i=0,n=0;
  float result=0;
  if(r%2==0) r++;
  while(n!=r)
  {
    if(x==0) result=1;
     else
     {
       if(++i%2!=0)
         result+=(x*sin(x,n-1)/n*sin(1,n-1));
        else
         result+=(-x*sin(x,n-1)/n*sin(1,n-1));
      }
       n+=2*n+1;
   }
  return result;
}

الفكرة ببساطة هي محاولة اختزال الدالتين fact و pow في دالة واحدة, هذه الدالة تستقبل متغيرين أحدهما float و الآخر int , بالنسبة لـ x فهي الزاوية المُراد حساب جيبها و المتغير r عبارة عن الأس الذي نتوقف عنده.

لاحظ أننا نزيد المتغير r بواحد عندما يكون زوجيا و ذلك لكي لا ندخل في دوارة غير منتهية !

الهدف من المتغير i هو تغيير إشارة الجزء المُضاف للمجموع في كل مرة فإذا كان i فرديا فإننا نضع إشارة موجبة أمام المجموع أما إذا كان i زوجيا فإننا نضع إشارة سالبة أمام المجموع .. لنوضح أكثر:

عند النظر إلى نشر تايلور لـ sin سنلاحظ أن الجزء المُضاف للمجموع تنعكس إشارته في كل مرة .. بمعنى :

يبدأ بإشارة موجبة و من ثم إشارة سالبة و من ثم يعود إلى الإشارة الموجبة و يحمل إشارة سالبة في المرة القادمة و هكذا دواليك .... :)

يمكننا استخدام متغير sign و نسند إليه قيمة ابتدائية هي -1 ثم نقوم بتغييرها في كل مرة لتتغير إشارة الجزء المُضاف للمجموع.

أما أنا فقد قمت بترقيم الأجزاء المُضافة للمجموع .. فأول جزء في المجموع يكون رقم 1 و ثان جزء يكون رقم 2 و ثالث جزء يكون رقم 3 و رابع جزء يكون رقم 4 و ....

لاحظ ان الأجزاء ذات الرتبة الفردية ستحمل إشارة موجبة أما الأجزاء ذات الرتية الزوجية فستحمل إشارة سالبة و هذا ما قصدتُه عندما كتبت:

if(++i%2!=0)
 result+=(x*sin(x,n-1)/n*sin(n-1));
else
 result+=(-x*sin(x,n-1)/n*sin(n-1));

هذا الجزء:

x*sin(x,n-1)

هو المسؤول عن حساب x أس n , أما هذا الجزء:

n*sin(n-1)

فيقوم بحساب مضروب n أو n!.

المتغير n فهو يمثل الأس في كل مرة و السبب في كتابة هذا السطر:

n+=2*n+1;

لأن الأس فردي دائما , لاحظ أننا نتوقف عندما تكون n تساوي r أي أن المتغير n يمثل الأس المؤقت.

------------------

إذا كان لا يزال يوجد استشكال فأرجوا منكم أن تحددوا لي رقم السطر في الكود :)

بانتظاركم يا أساتذتي :wink:

#10

السلام عليكم انا اخذت نسخة من الدالة لكي اتفحصها, لكن ان كانت المشكلة تحل باستخدام ثلاث دوال لماذا تريد دمجها بدالة واحدة؟؟ و الموضوع كان استخدام الاستدعاء الذاتي لكن لم يشر احد الى هذا الموضوع.

#11

السلام عليكم

اقتباس
ان كانت المشكلة تحل باستخدام ثلاث دوال لماذا تريد دمجها بدالة واحدة؟؟

أردت الإختصار فحسب .. و لكنني تفاجئت بأن الدالة تحتوي على خطأ (منطقي !) ولم أعرف أين هو بالتحدد :blush:

تحياتي.

#12

مبدأيا يوجد خطأ في الاستدعاء الذاتي.

فانت تستدعي الدالة مرتين استدعاء ذاتي ,

ضناً منك ان الاستدعاء الاول يعطي الاس

power والاستدعاء الثاني يعطي المفكوك factorial.

و ساقوم إن شاء الله بمعرفة الحل الصحيح.

#13
اقتباس
مبدأيا يوجد خطأ في الاستدعاء الذاتي.

فانت تستدعي الدالة مرتين استدعاء ذاتي ,

ضناً منك ان الاستدعاء الاول يعطي الاس

power والاستدعاء الثاني يعطي المفكوك factorial.

و ساقوم إن شاء الله بمعرفة الحل الصحيح.

ننتظر تصحيحك يا أستاذي الفاضل :wink:

#14

السلام عليكم

مرحبا من جديد واسف على التاخير

انا توصلت الى هذا البرنامج وهو يتكون من دالتين

لا نستطيع جعل البرنامج في دالة واحدة باستخدام

الاستدعاء الذاتي لان البرنامج يتطلب حلقتين (loop)

حيث الاولى تحسب الاس مع المفكوك والثانية تقوم

بجمع الناتج. على العموم هذا البرنامج اليكم الحكم.

#include<conio.h>
#include <iostream.h>

// *************** Mypf ***************
float mypf(int x,int r)
{
  static int sign=-1;
  float pow=1,fact=1;
  for(int j=1; j<=r; j++)
  	{
      	fact =fact *j;
        pow= pow *x;
    } 
  sign=-sign;
  return(sign*(pow/ fact));
}
//***************  My RecSin ***************
float myRecSin(int x, int r)
{
	if (r == 1)  		
		return (x);
	else
		return(mypf(x,r)+ myRecSin(x,r-1));
}
int main()
{   int x, r;
    cin >> x >>r;
    cout << myRecSin( x,r);
    getch();
    return 0;
}

تم تعديل هذه المشاركة بواسطة محمد علاء الدين في 6 أغسطس 2010 في 16:52 — السبب: لو تستخدم تاج الكود هتفرحنا و هتاخد +1 كمان

2
#15
اقتباس
انا توصلت الى هذا البرنامج وهو يتكون من دالتين

لا نستطيع جعل البرنامج في دالة واحدة باستخدام

الاستدعاء الذاتي لان البرنامج يتطلب حلقتين (loop)

حيث الاولى تحسب الاس مع المفكوك والثانية تقوم

بجمع الناتج. على العموم هذا البرنامج اليكم الحكم.

+1 على الكود الجميل :)

سلمت يمناك :wub:

تحياتي العطرة

تم تعديل هذه المشاركة بواسطة khaled_prg في 6 أغسطس 2010 في 18:58

مواضيع مشابهة

عدد الزوار حالياً

المتواجدون خلال آخر دقيقتين · يتحدّث كل ٣٠ ثانية

—الإجمالي—أعضاء مسجّلون—زوار بدون تسجيل

جارٍ التحقق من المتواجدين…