Menu
flag Ar iconالعربيةdown icon

الدوال الرياضية في لغة C: math.h وsqrt وpow والربط بـ -lm

جولة في math.h - sqrt وpow وfabs وfloor وceil وround وfmod وعائلتا المثلثات واللوغاريتمات وINFINITY وNAN - إضافة إلى راية الرابط -lm التي تسبّب خطأ المرجع غير المعرَّف الكلاسيكي.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

تمنحك لغة C في جوهرها + - * / و%، ولا شيء غير ذلك - لا عامل أسّ ولا جذر تربيعي ولا تقريب. وكل ما يتجاوز الحسابات يعيش في المكتبة الرياضية القياسية، المصرَّح عنها في <math.h>.

وتعمل هذه الدوال كلها بدقّة double: فهي تأخذ وسائط double وتُرجِع double. وذلك أول ما ينبغي استيعابه، لأن تمرير int ينجح بصمت (إذ يُحوَّل) بينما إسناد النتيجة إلى int يهمل الكسر بصمت.

البداية وراية -lm

وعلى جهازك، قد تفشل ترجمة ذلك:

/usr/bin/ld: /tmp/ccXYZ.o: in function `main':
program.c:(.text+0x1a): undefined reference to `sqrt'
collect2: error: ld returned 1 exit status

هذه أشيع عثرة على الإطلاق مع math.h، وهي ليست خطأً في شيفرتك. فقد وفّرت الترويسة التصريحات، فاقتنع المترجم؛ أما التنفيذات فتعيش في مكتبة منفصلة (libm) لا يضمّنها الرابط افتراضيًا على لينكس. أضف -lm:

gcc program.c -o program -lm

ويجب أن تأتي الراية بعد ملفات المصدر - فالروابط تعالج مدخلاتها بالترتيب، والمكتبة المُدرَجة قبل الشيفرة التي تحتاجها لا تحلّ شيئًا. وعلى macOS ومع MinGW على ويندوز، تكون الإجراءات الرياضية جزءًا من مكتبة C القياسية سلفًا، فـ -lm غير ضرورية (وإن كانت غير ضارّة على macOS).

القوى والجذور

تُرجِع sqrt(x) لعدد سالب قيمة NaN بدل الانهيار؛ فاحرس الوسيط إن كان يمكن أن يكون سالبًا. وhypot تستحق المعرفة لأنها تحسب القيمة نفسها التي تحسبها sqrt(x*x + y*y) دون فيضان حين تكون x أو y كبيرة جدًا.

وللقوى الصحيحة الصغيرة، يكون الضرب العادي أسرع وأدقّ - فـ x * x تتفوّق على pow(x, 2.0)، التي تمرّ باللوغاريتمات وقد تُرجِع 8.999999999999998 حيث توقّعت 9.

التقريب: floor وceil وround وtrunc

أربع دوال، وأربع إجابات مختلفة للأعداد السالبة. وذلك الجدول هو الجزء الجدير بالحفظ:

  • floor(x) - أكبر عدد صحيح لا يتجاوز x. وتتحرّك دائمًا نحو سالب اللانهاية: فـ floor(-2.3) تساوي -3.
  • ceil(x) - أصغر عدد صحيح لا يقلّ عن x. وتتحرّك دائمًا نحو موجب اللانهاية: فـ ceil(-2.3) تساوي -2.
  • round(x) - أقرب عدد صحيح، والأنصاف بعيدًا عن الصفر: فـ round(2.5) تساوي 3، وround(-2.5) تساوي -3.
  • trunc(x) - اقطع الجزء الكسري، بالتحرّك نحو الصفر: فـ trunc(-2.7) تساوي -2.

وتُرجِع الأربع كلها double. والتحويل إلى int حين تريد عددًا صحيحًا مقبول، لكن لاحظ أن (int)x وحدها تؤدّي اقتطاعًا - فـ (int)(x + 0.5) هي اصطلاح "التقريب" القديم، وهي خاطئة للأعداد السالبة. استخدم round().

fabs وfmod

fabs هي القيمة المطلقة للفاصلة العائمة. واللجوء إلى abs بدلًا منها خطأ كلاسيكي، لأن abs تأخذ int ويحدث التحويل بصمت:

لا تعمل % على double إطلاقًا - فهي عامل للأعداد الصحيحة ويرفضها المترجم. وfmod(a, b) هي نظيرها للفاصلة العائمة، وهي تحتفظ بإشارة a.

وfabs هي أيضًا الطريقة الصحيحة لمقارنة عددين من نوع double، إذ إن == على الفاصلة العائمة فخّ:

لا يمكن تمثيل 0.1 و0.2 تمثيلًا دقيقًا بالثنائي، فمجموعهما على بعد شعرة من 0.3. قارن بهامش تسامح، ولا تقارن بـ == أبدًا.

حساب المثلثات

تعمل دوال المثلثات بـالراديان لا بالدرجات - وهو المصدر الموثوق الآخر للإجابات الخاطئة.

المجموعة الكاملة: sin وcos وtan، وعكوسها asin وacos وatan، وatan2(y, x) ذات الوسيطين، والعائلة الزائدية sinh وcosh وtanh. وفضّل atan2(y, x) على atan(y / x) للزوايا - فهي تعالج x == 0 وتصيب الربع.

وتعرّف math.h الثابت M_PI على معظم الأنظمة، لكنه امتداد من POSIX لا C معيارية، فقد يغيب تحت -std=c17. وتعريف ثابتك الخاص، كما سبق، قابل للنقل.

اللوغاريتمات والأسّيات

لاحظ فخّ التسمية المنقول من الرياضيات: فـ log() هي اللوغاريتم الطبيعي (بالأساس e)، لا الأساس 10. والأساس 10 هو log10().

وتُرجِع log(0.0) سالب اللانهاية وتُرجِع log(-1.0) قيمة NaN - ولا واحدة منهما تنهار، ما يعني أن وسيطًا سيّئًا يسافر بصمت عبر حسابك حتى يطبع شيء ما nan.

INFINITY وNAN وفحصهما

للفاصلة العائمة قيم ليست أعدادًا، وmath.h تسمّيها:

والسطر الأخير هو الحاسم. لا تستطيع كتابة if (x == NAN) - فهي خاطئة دائمًا. استخدم isnan(x). (والمطابقة x != x صحيحة لـ NaN وحدها، وهي كيف تُنفَّذ isnan غالبًا وبديل مفيد إن صادفت يومًا سلسلة أدوات بلا هذه الدالة.)

وتصل اللانهايات من الفيضان (exp(1000.0)) ومن قسمة عدد double غير صفري على صفر، وهي - بخلاف القسمة الصحيحة على صفر - معرَّفة ولا تنهار.

صيغتا float وlong double

لكل دالة ثلاث صيغ: صيغة double، وصيغة float بلاحقة f، وصيغة long double بلاحقة l.

float       sqrtf(float x);
double      sqrt(double x);
long double sqrtl(long double x);

استخدم صيغ double العادية ما لم يكن لديك سبب مقيس لغير ذلك - فـ double هو ما تُرقّي إليه C افتراضيًا، وإقحام صيغ float يكلّف في التحويلات عادةً أكثر مما يوفّر. والموضع الوحيد الذي تهمّ فيه اللاحقة هو حين يكون حساب float وحده حسّاسًا للأداء على عتاد بلا دعم لـ double.

مثال عملي

جمعًا لعدة من هذه - المسافة بين نقطتين وجذرا معادلة تربيعية:

لاحظ fabs(disc) < 1e-12 بدل disc == 0.0 - قاعدة التسامح نفسها السابقة، مطبَّقة حيث لن يقع مميّز محسوب من قيم مقيسة على الصفر بالضبط إلا نادرًا جدًا.

و<math.h> ترويسة واحدة في صندوق أدوات أكبر؛ وتخطّط صفحة المكتبة القياسية الباقي، وتغطّي الأعداد العشوائية الدالةَ rand التي تعيش في <stdlib.h> لا هنا.

الأسئلة الشائعة

كيف أستخدم الدوال الرياضية في لغة C؟

أضف #include <math.h> في أعلى الملف واستدعِها: sqrt(16.0) وpow(2.0, 10.0) وfabs(-3.5). وهي تأخذ وتُرجِع double. وعلى لينكس يجب أيضًا ربط المكتبة الرياضية بـ -lm: gcc program.c -o program -lm.

لماذا أحصل على 'undefined reference to sqrt' عند الترجمة؟

أعطت الترويسةُ المترجمَ التصريح، لكن التنفيذ يعيش في مكتبة منفصلة لا يجلبها الرابط تلقائيًا على لينكس. أضف -lm في آخر الأمر: gcc program.c -o program -lm. وعلى macOS ومع MinGW على ويندوز تكون الشيفرة الرياضية جزءًا من مكتبة C القياسية، فلا تلزم أي راية.

ما الفرق بين abs وfabs في لغة C؟

abs() من <stdlib.h> تأخذ int وتُرجِع int. وfabs() من <math.h> تأخذ double وتُرجِع double. واستدعاء abs(-3.7) يحوّل الوسيط إلى int أولًا ويعطي 3، مهملًا الكسر بصمت - استخدم fabs للقيم ذات الفاصلة العائمة.

كيف أفحص NaN في لغة C؟

استخدم isnan(x) من <math.h>. ولا تستطيع الفحص بـ x == NAN، لأن NaN لا تساوي أي شيء بما في ذلك نفسها - وتلك الغرابة هي في الواقع الفحص الاحتياطي: فـ x != x صحيحة لـ NaN وحدها. واستخدم isinf(x) للانهايات وisfinite(x) للتحقّق من عدد عادي.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن