ال

الأعداد في Julia

7 تمارين

نبذة عن الأعداد

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

الأعداد الصحيحة

العدد الصحيح هو عدد «مستدير» بلا فاصلة عشرية.

في مفهوم الأساسيات، رأينا أنه يمكن إسناد قيمة عدد صحيح إلى متغير دون تحديد نوع.

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

julia> x = 3
3

julia> typeof(x)
Int64

julia> large_number = 1_234_567_890
1234567890

داخليًا، سيستخدم المترجم نوع العدد الصحيح المُوقَّع الأنسب لمعالج حاسوبك. على الحواسيب الشخصية الحديثة، سيكون هذا عادةً Int64، وهو مناسب تمامًا لمعظم المهام.

ستُناقش الأنواع بمزيد من التفصيل في مفهوم لاحق. في الوقت الحالي، نأمل أن تكون الأنواع العددية بديهية بما يكفي ليصبح لهذا المستند معنى.

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

لمنح المبرمج تحكمًا أدق، من الممكن والشائع جدًا تحديد نوع العدد الصحيح المُوقَّع أو غير المُوقَّع المطلوب. قد يرغب كثير من متعلمي جوليا الذين استخدموا Python سابقًا في مقارنة هذا بـ NumPy، بدلًا من Python الأساسية.

julia> y = Int8(42)
42

julia> typeof(y)
Int8

julia> z = UInt32(1024)
0x00000400

لاحظ أن جوليا تعرض افتراضيًا الأعداد الصحيحة غير المُوقَّعة بصيغة ست عشرية، لأن الأنواع غير المُوقَّعة تُستخدم غالبًا في عمليات البتات منخفضة المستوى.

يمكن أيضًا إدخال الأعداد الصحيحة بالنظام الثنائي أو الثماني أو الست عشري، بالبادئات 0b و0o و0x على التوالي.

julia> a = 0x10
0x10

julia> Int(a)
16

julia> b = 0b1010 # displays as hexadecimal
0x0a

تجاوز سعة الأعداد الصحيحة وBigInt

لكل نوع عدد صحيح قيمة عظمى وقيمة صغرى يمكنه تخزينها:

julia> typemax(Int8)
127

julia> typemin(Int8)
-128

الخروج عن هذا النطاق الصالح سيؤدي إلى تجاوز سعة العدد الصحيح، بنتائج تبدو غريبة جدًا.

julia> c = Int8(126)
126

julia> c * c
4

لعلنا نتفق أن 126 * 126 لا ينبغي أن يساوي 4!

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

يمكن تقليل المشكلة باستخدام نوع «أوسع»، حتى Int128 أو UInt128 (ستُناقش معنى «أوسع» أدناه). لكن جميعها لها حدود صارمة قبل الوقوع في مشكلات تجاوز السعة.

أحد الحلول هو استخدام نوع BigInt، الذي تحدّه ذاكرة حاسوبك فقط. وهو تطبيق جوليا لـ الحساب ذي الدقة العشوائية.

julia> 2 ^ 70
0

julia> big = BigInt(2) ^ 70
1180591620717411303424

julia> UInt128(big)
0x00000000000000400000000000000000

لأن 2 مرفوعًا للقوة 70 لن يتسع في Int64، فإن الحساب الأول في المثال أعلاه يفشل: مع ملاحظة أنه لا توجد رسالة خطأ. فليحترس المبرمج!

تحويل العدد 2 إلى BigInt يعطي الإجابة الصحيحة، وعرضه بصيغة ست عشرية يوضّح أكثر أنه قوة للعدد 2.

أعداد الفاصلة العائمة

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

julia> f = 3.45
3.45

julia> typeof(f)
Float64

وبالطبع، الترميز العلمي مدعوم.

julia> avogadro = 6.02e23
6.02e23

كما هو الحال مع الأعداد الصحيحة، النوع الافتراضي مناسب لمعظم الأغراض، لكن هناك أنواع أخرى موقّعة متاحة. لا توجد أنواع فاصلة عائمة غير موقّعة.

كاختصار، يمكن إنشاء قيم Float32 باستخدام اللاحقة f0.

julia> f32 = 4.56f0
4.56f0

julia> typeof(f32)
Float32

تحتاج الأحجام الأخرى من أعداد الفاصلة العائمة إلى تحويل صريح، كما هو الحال مع الأعداد الصحيحة.

قد تفاجئك القيمتان العظمى والصغرى:

julia> typemax(Float64)
Inf

julia> typemin(Float64)
-Inf

اللانهاية قيمة صالحة!

لكن النطاق المفيد لأعداد الفاصلة العائمة محدود، فكل قيمة ضخمة جدًا تُسند إليها قيمة Inf، وكل قيمة صغيرة جدًا تُقرَّب إلى 0.0.

يمكننا استخدام زوج آخر من الدوال لرؤية هذين الحدين: تقريبًا ± 10 ^ 308.

julia> floatmax(Float64)
1.7976931348623157e308

julia> floatmin(Float64)
2.2250738585072014e-308

كذلك الدقة محدودة، إذ يُمثَّل عدد كبير لكن منتهٍ من الأرقام المعنوية (حوالي 15 لـ Float64).

عوامل الحساب

كما نوقش في مفهوم الأساسيات، تعمل عوامل الحساب غالبًا بالطريقة نفسها التي يعمل بها الحساب القياسي كما يُعلَّم للأطفال. لاحظ أن الرفع إلى قوة يستخدم ^، وليس ** (كلاهما شائع في لغات أخرى).

2 + 3  # 5 (addition)
2 - 3  # -1 (subtraction)
2 * 3  # 6 (multiplication)
8 / 2  # 4.0 (division)
8 % 3  # 2 (remainder)
2 ^ 3  # 8 (exponentiation)

لكن هناك بعض التفاصيل الخاصة بجوليا التي تستحق المناقشة.

الضرب

julia> x = 4.2
4.2

julia> 2 * x
8.4

julia> 2x
8.4

julia> 2.4x
10.08

قد يكون هذا مفاجئًا.

من الممكن دائمًا استخدام * كـ عامل بيني، كما هو الحال في معظم لغات الحاسوب الأخرى.

لكن جوليا صممها أشخاص يؤمنون بأن الكود ينبغي أن يبدو قدر الإمكان مثل المعادلات الرياضية.

ولأن أسماء المتغيرات يجب أن تبدأ بحرف، فإن وضع رقم (صحيح أو فاصلة عائمة) قبل الاسم يُعامَل كضرب ضمني.

مثلًا، إذا أردنا مساحة سطح كرة، فبدلًا من 4 * pi * r * r يمكننا فعل هذا:

julia> surface(r) = 4π * r^2
surface (generic function with 1 method)

julia> surface(3)
113.09733552923255

على الرغم من أن π ثابت مدمج، إلا أنه أيضًا حرف (يوناني). لذلك لا يزال المُحلِّل بحاجة إلى * صريح واحد لفصل π عن r.

القسمة

استخدام / كـ عامل بيني سيعطي دائمًا نتيجة فاصلة عائمة، حتى مع مدخلات صحيحة.

لقسمة الأعداد الصحيحة، هناك خيارات أكثر:

julia> 10 / 3
3.3333333333333335

julia> div(10, 3)
3

julia> 10 ÷ 3
3

julia> 10 // 3
10//3

دالة div() مخصصة لقسمة الأعداد الصحيحة، وتُقرَّب النتيجة نحو الصفر: إلى الأسفل للأعداد الموجبة، وإلى الأعلى للأعداد السالبة.

وكمرادف، يمكننا استخدام العامل البيني ÷، بهدف جعله يبدو أكثر رياضية. إذا كنت تستخدم محررًا يعرف جوليا، فأدخل هذا عبر كتابة \div ثم اضغط مفتاح <Tab>.

سيحتاج العامل // إلى مفهوم خاص به، لاحقًا في المنهج.

في الوقت الحالي، يمكننا القول إن ناتج // هو عدد «نسبي»، الاسم الرسمي لما يسميه معظم الناس كسرًا.

تُزال العوامل المشتركة من البسط والمقام، لإعطاء نسبة بين عددين صحيحين فيما يُسمى «أبسط صورة».

julia> rationalnum = 22 // 6
11//3

julia> typeof(rationalnum)
Rational{Int64}

لدينا أعداد نسبية. فماذا عن الأعداد «غير النسبية»؟

julia> π
π = 3.1415926535897...

julia> typeof(π)
Irrational{:π}

العدد غير النسبي هو عدد لا يمكن اختزاله إلى نسبة بين عددين صحيحين. من الأمثلة الشائعة π وe (عدد أويلر)، وكثير من الجذور مثل √2 (الجذر التربيعي لـ 2).

تحاول جوليا أن تتعامل مع الرياضيات على نحو صحيح.

تحويل الأنواع العددية

يمكن أن يحدث هذا غالبًا تلقائيًا:

julia> x = 2 + 3.5
5.5

julia> typeof(x)
Float64

أضفنا Int64 إلى Float64، فحصلنا على نتيجة Float64.

في الواقع، حُوِّل العدد الصحيح بصمت إلى Float64 قبل إجراء الجمع.

لدى جوليا مفهوم «العرض» للأنواع العددية.

  • ضمن الأعداد الصحيحة، وضمن أعداد الفاصلة العائمة، هو مجرد عدد البتات اللازمة للتخزين. وهكذا، يكون Int64 أوسع من Int16.
  • تُعدّ أعداد الفاصلة العائمة أوسع من الأعداد الصحيحة، لأنها تستطيع تخزين أي جزء كسري.

إذا استُخدم مزيج من الأنواع داخل تعبير، يُرقّى كل منها حسب الحاجة إلى أوسع نوع مستخدم.

لفرض التحويل، يمكننا تحويل عدد صحيح إلى نوع محدد، كما في Float64(5).

بدلًا من ذلك، استخدم float(5) فقط ودع المترجم يختار نوعًا مناسبًا.

تحويلات الفاصلة العائمة إلى الأعداد الصحيحة أكثر تعقيدًا حتمًا. ماذا تريد أن تفعل بأي شيء بعد الفاصلة العشرية؟

  • دالة round() تحوّل إلى أقرب عدد صحيح، والتعادلات مثل 4.5 تُقرَّب إلى أقرب عدد صحيح زوجي.
  • floor() يُقرِّب للأسفل، وceil() يُقرِّب للأعلى، وtrunc() يُقرِّب نحو الصفر.
  • محاولة التحويل مباشرة، مثلًا بـ Int32()، ستفشل بخطأ InexactError.

لكن افتراضيًا، لا تُرجع هذه الدوال نوع العدد الصحيح الذي قد تكون أردته. يمكن تحديد نوع الناتج المطلوب.

julia> round(4.5)
4.0

julia> round(Int64, 4.5)
4

julia> round(Int, 4.5)  # => default integer type
4

julia> ceil(Int16, 4.3)
5

التقريب إلى عدد محدد من الأرقام بعد الفاصلة العشرية ممكن أيضًا باستخدام الكلمة المفتاحية digits.

julia> round(π, digits=10)
3.1415926536

راجع الدليل لمزيد من التفاصيل.

القسمة على صفر

ألا يؤدي هذا ببساطة إلى خطأ؟ في الواقع، الأمر ليس بهذه البساطة.

قسمة الأعداد الصحيحة بـ ÷ أو // ستؤدي إلى خطأ، كما قد تتوقع.

أما قسمة الفاصلة العائمة بـ / فتتّبع نهجًا قد يُعتبر هندسيًا، بدلًا من نهج علوم الحاسوب القياسي:

julia> 2 / 0
Inf

julia> 0 / 0
NaN

كما نوقش في قسم سابق، اللانهاية عدد صالح للفاصلة العائمة في جوليا، ويُمثَّل بـ Inf.

عندما يكون البسط صفرًا أيضًا، تكون النتيجة غير معرّفة رياضيًا. عندئذٍ تتعامل معها جوليا على أنها «ليس عددًا»، ويُمثَّل بـ NaN.

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

الفحص اليدوي اللانهائي للقيم سيكون مملًا في البرمجة، وسيضر بالتأكيد بأداء وقت التشغيل.

التوقف برسالة خطأ عند كل خلل طفيف سيجعل برنامجك غير محبوب جدًا لدى المستخدمين!

مقارنة قيم الفاصلة العائمة

كما هو موضح في مفهوم Conditionals، يُجرى اختبار التساوي عادةً بالعامل ==.

يعمل هذا جيدًا مع الأعداد الصحيحة والمحارف والسلاسل النصية وما إلى ذلك. لكن قيم الفاصلة العائمة لها دقة محدودة، وقد تؤدي طرق مختلفة لحساب النتيجة نفسها إلى قيم مختلفة قليلًا. بالنسبة لـ Float64، سيكون هذا عادةً حول الرقم المعنوي الخامس عشر: فرق صغير، لكنه ليس «تساويًا».

تقليديًا، النصيحة للمبرمجين هي ألا يستخدموا == أبدًا مع قيم الفاصلة العائمة: فالنتائج غير متوقعة.

البديل الشائع هو اختبار القيمة المطلقة للفرق مقابل تفاوت مسموح ما (يُسمى غالبًا إبسيلون أو ϵ). فبدلًا من a == b، استخدم abs(a - b) < epsilon.

توفّر جوليا بديلًا أنظف عبر دالة isapprox().

بالنسبة لتفاوت مطلق، تكون الصياغة isapprox(a, b, atol=epsilon). الكلمة المفتاحية atol مطلوبة في هذه الحالة.

التفاوت النسبي غالبًا أكثر فائدة. هذا هو الوضع الافتراضي، لذا سيحاول isapprox(a, b) اختيار قيمة معقولة لـ rtol (القواعد معقدة جدًا).

يمكن أيضًا تحديد تفاوت نسبي، ككسر من القيم المقارَنة. فيختبر isapprox(a, b, rtol=0.01 أن القيمتين ضمن 1% من بعضهما.

وعلى النهج المعتاد في جوليا، هناك عامل رياضي مرادف للحالة الافتراضية: a ≈ b (حيث يُدخل العامل بكتابة \approx ثم <tab>).

مفاهيم مستقبلية ذات صلة

إلى جانب الأعداد النسبية، ستناقش أجزاء لاحقة من المنهج:

تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة

تعلّم الأعداد

التدريب مقفل

افتح تمرينين إضافيين لممارسة الأعداد