عدد النقطة العائمة هو عدد يمكن أن يحمل جزءًا كسريًا، مثل -1.23 أو 3.1415.
ولتمثيل عدد كهذا على هيئة تسلسل من البايتات، يستخدم x86-64 معيار IEEE-754. أكثر صيغتين شيوعًا في هذا المعيار هما أعداد النقطة العائمة ذات الدقة المفردة وأعداد النقطة العائمة ذات الدقة المزدوجة. ويدعم x86-64 كلتيهما.
عدد النقطة العائمة ذو الدقة المفردة بعرض 32 بت، بينما ذو الدقة المزدوجة بعرض 64 بت.
في 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
في هذا التمرين، ستكتب كودًا يساعد مستقلًا على التواصل مع مدير مشروع. مهمتك هي توفير بعض الأدوات المساعدة لحساب المعدلات اليومية والشهرية بسرعة، مع إمكانية تطبيق خصم معيّن.
نضع أولًا بعض القواعد بين المستقل ومدير المشروع:
أحيانًا يعرض المستقل تطبيق خصم على معدله اليومي (مثلًا لعملائه الأكثر ولاءً أو للعملاء غير الربحيين).
يُمثَّل الخصم بأعداد عشرية تمثل نسبًا مئوية، على سبيل المثال 25.0 (25%).
هذه هي اللاحقات الخاصة بتعليمات الفاصلة العائمة:
| اللاحقة | حجم المعامل |
|---|---|
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 | الاقتطاع |
نفّذ دالة باسم daily_rate لحساب المعدل اليومي، ويُمرَّر إليها معامل هو المعدل في الساعة، على هيئة عدد ذي فاصلة عائمة بطول 64 بت.
ينص العقد على أن اليوم فيه 8 ساعات قابلة للفوترة.
daily_rate(60)
// => 480.0
يجب أن يكون المعدل اليومي المُرجَع عددًا ذا فاصلة عائمة بطول 64 بت.
نفّذ دالة apply_discount لحساب السعر بعد الخصم.
يجب أن تقبل معاملين: السعر الأصلي ونسبة الخصم بالمئة، وكلاهما على هيئة عدد ذي فاصلة عائمة بطول 64 بت.
apply_discount(150.0, 10.0)
// => 135.0
يجب أن تكون القيمة المُرجَعة دائمًا عددًا ذا فاصلة عائمة بطول 64 بت.
نفّذ دالة monthly_rate لحساب المعدل الشهري بعد الخصم.
تأخذ معاملين: المعدل في الساعة ونسبة الخصم بالمئة، وكلاهما على هيئة عدد ذي فاصلة عائمة بطول 64 بت.
monthly_rate(77.0, 10.5)
// => 12130
يجب تقريب المعدل الشهري المُرجَع لأعلى (أي أخذ السقف) إلى أقرب عدد صحيح بطول 64 بت.
نفّذ دالة days_in_budget تأخذ المعاملات التالية:
يجب أن تُرجع هذه الدالة عدد أيام العمل الكاملة التي تغطيها الميزانية.
days_in_budget(20000, 80, 11.0)
// => 35
يجب تقريب عدد الأيام المُرجَع لأسفل (أي أخذ الأرضية) إلى أقرب عدد صحيح بطول 32 بت.
سجّل في Exercism لتتعلّم وتتقن x86-64 Assembly عبر 22 مفهومًا130 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.