المسارات
/
x86-64 Assembly
x86-64 Assembly
/
التمارين
/
أسعار المستقلّين
أسعار المستقلّين

أسعار المستقلّين

تمرين تعلّمي

مقدمة

أعداد النقطة العائمة

عدد النقطة العائمة هو عدد يمكن أن يحمل جزءًا كسريًا، مثل -1.23 أو 3.1415.

ولتمثيل عدد كهذا على هيئة تسلسل من البايتات، يستخدم x86-64 معيار IEEE-754. أكثر صيغتين شيوعًا في هذا المعيار هما أعداد النقطة العائمة ذات الدقة المفردة وأعداد النقطة العائمة ذات الدقة المزدوجة. ويدعم x86-64 كلتيهما.

عدد النقطة العائمة ذو الدقة المفردة بعرض 32 بت، بينما ذو الدقة المزدوجة بعرض 64 بت.

سجلات XMM

في x86-64، هناك 16 سجلًا مخصصًا لإجراء الحساب بالنقطة العائمة، تُسمّى xmm0 حتى xmm15.

هذه السجلات بعرض 128 بت على الأقل، لكن المعالجات الحديثة ترفع ذلك إلى 256 بت أو 512 بت. ومع ذلك، في الممارسة العملية، لا تستخدم معظم أعداد النقطة العائمة سوى أقل 32 بت أو 64 بت، حسب الدقة المطلوبة.

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

هناك عائلة من التعليمات للتحويل بين صيغ الأعداد: cvtxx2yy.

كلا xx وyy ليسا حرفيين، بل عنصران نائبان:

  • xx عنصر نائب لنوع المُعامل المصدر.
  • yy عنصر نائب لنوع المُعامل الهدف.

ويمكن أن يأخذ هذا العنصر النائب أشكالًا عديدة، منها:

العنصر النائب المعنى
si عدد صحيح مُوقَّع
ss عدد نقطة عائمة 32 بت
sd عدد نقطة عائمة 64 بت

على سبيل المثال:

cvtsi2ss xmm0, eax ; converts a 32-bit signed integer to a single-precision floating-point
cvtsd2si rax, xmm1 ; converts a double-precision floating-point to a 64-bit signed integer

لاحظ أنه من الممكن تحويل عدد صحيح 32 بت إلى عدد نقطة عائمة 64 بت، أو عدد صحيح 64 بت إلى عدد نقطة عائمة 32 بت. وبالمثل، من الممكن أيضًا تحويل عدد نقطة عائمة 32 بت إلى عدد صحيح 64 بت، أو عدد نقطة عائمة 64 بت إلى عدد صحيح 32 بت. بعبارة أخرى، حجم كل من المُعاملين في cvtxx2yy لا يلزم أن يكونا متماثلين:

cvtsi2sd xmm0, eax ; converts a 32-bit signed integer to a double-precision floating-point
cvtsi2ss xmm0, rax ; converts a 64-bit signed integer to a single-precision floating-point
cvtss2si rax, xmm0 ; converts a single-precision floating-point to a 64-bit signed integer
cvtsd2si eax, xmm0 ; converts a double-precision floating-point to a 32-bit signed integer

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

لاحظ أن النتيجة الدقيقة لا تُقرّب، وبالتالي لا تتأثر بالتحكم في التقريب. على سبيل المثال، سيُحوَّل 42.0 دائمًا إلى 42.

وهناك عائلة مشابهة من التعليمات بالصيغة cvttxx2si (مع حرف t إضافي) تقتطع النتيجة دائمًا.

التقريب

تُحوّل عائلة التعليمات roundxx عدد نقطة عائمة في المُعامل المصدر إلى عدد نقطة عائمة مُقرَّب يُخزَّن في المُعامل الهدف.

هنا، xx عنصر نائب لنوع المُعاملين، كما هو معتاد: ss للدقة المفردة وsd للدقة المزدوجة.

تأخذ هذه التعليمات عددًا ثابتًا كمُعامل ثالث. ويشير هذا العدد إلى نمط التقريب:

العدد النمط
0 الأقرب
1 الأرضية (للأسفل)
2 السقف (للأعلى)
3 الاقتطاع

على سبيل المثال:

roundss xmm0, xmm1, 1 ; this rounds down (takes the floor) a 32-bit floating-point in xmm1 and stores it in xmm0
roundsd xmm2, xmm2, 2 ; this rounds up (takes the ceil) a 64-bit floating-point in xmm2 and stores it in xmm2

بما أن هذه التعليمات تُنتج نتيجة دقيقة، فيمكن استخدامها تمهيدًا لـ cvtxx2si.

تعليمات الحساب

تتخذ العديد من العمليات على أعداد النقطة العائمة تعليمات تحمل اسمًا مشابهًا لنظيرتها الخاصة بالأعداد الصحيحة، مع إضافة العنصر النائب اللازم (ss أو sd) في النهاية. وهذه التعليمات ذات مُعاملين، بدلالتها المعتادة، حتى تلك التي تُجري الضرب أو القسمة:

addss xmm1, xmm2 ; xmm1 = xmm1 + xmm2 (all 32-bit floating-point)
mulsd xmm4, xmm5 ; xmm4 = xmm4 * xmm5 (all 64-bit floating-point)
divss xmm0, xmm3 ; xmm0 = xmm0 / xmm3 (all 32-bit floating-point)

النقل

التعليمات المستخدمة للنقل من سجل XMM إلى آخر هي movss أو movsd:

movss xmm1, xmm0 ; this copies a 32-bit floating-point from xmm0 to xmm1
movsd xmm6, xmm4 ; this copies a 64-bit floating-point from xmm4 to xmm6

لا يمكن نقل قيمة فورية (عدد صحيح مُوقَّع ثابت) إلى سجل xmm مباشرةً. يجب أولًا نقل هذا العدد إلى سجل عام الغرض ثم تحويله باستخدام cvtsi2xx:

mov rax, 10        ; rax is a 64-bit integer with value 10
cvtsi2sd xmm0, rax ; xmm0 is a 64-bit floating-point with value 10.0

المقارنات

هناك مجموعتان من التعليمات لإجراء مقارنة بين عددين من أعداد النقطة العائمة. تأخذ هاتان المجموعتان الصيغة comixx وucomixx، حيث xx إما ss أو sd. وكلتاهما تضبطان الأعلام في rflags كما هو معتاد، وفق الدلالة نفسها التي يتبعها cmp.

والفرق بينهما أن ucomixx تضبط parity flag (PF) إذا كان أحد عددَي النقطة العائمة يحمل القيمة NaN، بينما ترفع comixx استثناءً في هذه الحالة.

والمقارنة بين قيم النقطة العائمة غير مُوقَّعة:

ucomisd xmm0, xmm1
ja .greater         ; 'ja' must be used for "greater", not 'jg'
jb .lesser          ; 'jb' must be used for "lesser", not 'jl'

اصطلاح الاستدعاء

في System V ABI، تُمرَّر الوسائط الثمانية الأولى من أعداد النقطة العائمة إلى الدوال بالترتيب، من xmm0 إلى xmm7. وتُرجَع قيم النقطة العائمة من الدوال في xmm0، وعند الحاجة في xmm1.

جميع سجلات xmm متاحة للاستخدام بحرية، ولا يُحجز أي منها للمُستدعي.

الذاكرة

يمكن تعريف قيم النقطة العائمة في الذاكرة وفق الدلالة نفسها المتبعة مع الأعداد الصحيحة بالحجم نفسه. كما يسمح NASM (The Netwide Assembler، وهو المُجمِّع الذي يستخدمه هذا المسار) بالإعلان عن قيم النقطة العائمة بالترميز العلمي.

section .data
    pi dq 3.141592653589793 ; this is a double-precision floating-point value
    exp_num dd 1.e5 ; this is a single precision floating-point value equal to 1 * 10⁵
    exp_num2 dd 1.e+5 ; this is a single precision floating-point value equal to 1 * 10⁵
    frac dq 1.e-10 ; this is a double precision floating-point value equal to 1 * 10⁻¹⁰, i.e., 0.0000000001

التعليمات

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

نضع أولًا بعض القواعد بين المستقل ومدير المشروع:

  • المعدل اليومي يساوي 8 أضعاف المعدل في الساعة.
  • الشهر فيه 22 يومًا قابلة للفوترة.

أحيانًا يعرض المستقل تطبيق خصم على معدله اليومي (مثلًا لعملائه الأكثر ولاءً أو للعملاء غير الربحيين).

يُمثَّل الخصم بأعداد عشرية تمثل نسبًا مئوية، على سبيل المثال 25.0 (25%).

Note

هذه هي اللاحقات الخاصة بتعليمات الفاصلة العائمة:

اللاحقة حجم المعامل
ss 32 بت
sd 64 بت

هذه بعض التعليمات المذكورة في هذا المفهوم:

التعليمة الوصف
movss/movsd a, b ينسخ b إلى a
addss/addsd a, b a = a + b
subss/subsd a, b a = a - b
mulss/mulsd a, b a = a * b
divss/divsd a, b a = a / b
cvtsi2ss/cvtsi2sd a, b يحوّل العدد الصحيح b إلى عدد ذي فاصلة عائمة a
roundss/roundsd a, b, c يقرّب العدد ذا الفاصلة العائمة b وفقًا لـ c ويخزّنه في a
cvtss2si/cvtsd2si a, b يحوّل العدد ذا الفاصلة العائمة b إلى عدد صحيح a
comiss/comisd a, b يجري مقارنة بين a و b، ويُطلق استثناءً إذا كانت القيمة NaN
ucomiss/ucomisd a, b يجري مقارنة بين a و b، ويضبط PF إذا كانت القيمة NaN

هذه هي أرقام التحكم في التقريب في roundss و roundsd:

الرقم النمط
0 الأقرب
1 التقريب لأسفل
2 التقريب لأعلى
3 الاقتطاع

1. احسب المعدل اليومي انطلاقًا من المعدل في الساعة

نفّذ دالة باسم daily_rate لحساب المعدل اليومي، ويُمرَّر إليها معامل هو المعدل في الساعة، على هيئة عدد ذي فاصلة عائمة بطول 64 بت. ينص العقد على أن اليوم فيه 8 ساعات قابلة للفوترة.

daily_rate(60)
// => 480.0

يجب أن يكون المعدل اليومي المُرجَع عددًا ذا فاصلة عائمة بطول 64 بت.

2. احسب السعر بعد الخصم

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

apply_discount(150.0, 10.0)
// => 135.0

يجب أن تكون القيمة المُرجَعة دائمًا عددًا ذا فاصلة عائمة بطول 64 بت.

3. احسب المعدل الشهري انطلاقًا من المعدل في الساعة ونسبة الخصم

نفّذ دالة monthly_rate لحساب المعدل الشهري بعد الخصم. تأخذ معاملين: المعدل في الساعة ونسبة الخصم بالمئة، وكلاهما على هيئة عدد ذي فاصلة عائمة بطول 64 بت.

monthly_rate(77.0, 10.5)
// => 12130

يجب تقريب المعدل الشهري المُرجَع لأعلى (أي أخذ السقف) إلى أقرب عدد صحيح بطول 64 بت.

4. احسب عدد أيام العمل الكاملة انطلاقًا من الميزانية والمعدل في الساعة ونسبة الخصم

نفّذ دالة days_in_budget تأخذ المعاملات التالية:

  1. ميزانية، على هيئة عدد صحيح بدون إشارة بطول 64 بت.
  2. المعدل في الساعة، على هيئة عدد ذي فاصلة عائمة بطول 64 بت.
  3. نسبة الخصم بالمئة، على هيئة عدد ذي فاصلة عائمة بطول 64 بت.

يجب أن تُرجع هذه الدالة عدد أيام العمل الكاملة التي تغطيها الميزانية.

days_in_budget(20000, 80, 11.0)
// => 35

يجب تقريب عدد الأيام المُرجَع لأسفل (أي أخذ الأرضية) إلى أقرب عدد صحيح بطول 32 بت.

تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
x86-64 Assembly Exercism

مستعد لبدء أسعار المستقلّين؟

سجّل في Exercism لتتعلّم وتتقن x86-64 Assembly عبر 22 مفهومًا130 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.