مسیرها
/
x86-64 Assembly
x86-64 Assembly
/
تمرین‌ها
/
نرخ‌های فریلنسر
نرخ‌های فریلنسر

نرخ‌های فریلنسر

تمرین یادگیری

مقدمه

اعداد ممیز شناور

ممیز شناور به عددی گفته می‌شود که می‌تواند بخش کسری داشته باشد، مثل -1.23 یا 3.1415.

برای نمایش چنین عددی به‌صورت دنباله‌ای از بایت‌ها، x86-64 از استاندارد IEEE-754 استفاده می‌کند. دو قالب رایج‌تر در این استاندارد، اعداد ممیز شناور با دقت ساده و اعداد ممیز شناور با دقت مضاعف هستند. x86-64 از هر دو پشتیبانی می‌کند.

یک عدد ممیز شناور با دقت ساده ۳۲ بیت عرض دارد، در حالی که دقت مضاعف ۶۴ بیت عرض دارد.

ثبات‌های XMM

در x86-64، ۱۶ ثبات اختصاصی برای انجام حساب ممیز شناور وجود دارد که از xmm0 تا xmm15 نام‌گذاری شده‌اند.

این ثبات‌ها حداقل ۱۲۸ بیت عرض دارند، اما پردازنده‌های جدید این مقدار را به ۲۵۶ یا ۵۱۲ بیت افزایش می‌دهند. با این حال، در عمل بیشتر اعداد ممیز شناور فقط از پایین‌ترین ۳۲ یا ۶۴ بیت استفاده می‌کنند، بسته به دقتی که مورد نظر است.

تبدیل بین اعداد

خانواده‌ای از دستورها برای تبدیل بین قالب‌های عددی وجود دارد: cvtxx2yy.

هر دو xx و yy نه مقدار ثابت، بلکه «جای‌نگهدار» هستند:

  • xx جای‌نگهدار نوع عملوند مبدأ است.
  • yy جای‌نگهدار نوع عملوند مقصد است.

این جای‌نگهدار می‌تواند شکل‌های گوناگونی داشته باشد، از جمله:

جای‌نگهدار معنی
si عدد صحیح علامت‌دار
ss ممیز شناور ۳۲ بیتی
sd ممیز شناور ۶۴ بیتی

برای نمونه:

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

توجه کنید که می‌توان یک عدد صحیح ۳۲ بیتی را به یک ممیز شناور ۶۴ بیتی یا یک عدد صحیح ۶۴ بیتی را به یک ممیز شناور ۳۲ بیتی تبدیل کرد. به همین ترتیب، می‌توان یک ممیز شناور ۳۲ بیتی را به یک عدد صحیح ۶۴ بیتی یا یک ممیز شناور ۶۴ بیتی را به یک عدد صحیح ۳۲ بیتی تبدیل کرد. به عبارت دیگر، اندازه‌ی هر دو عملوند در یک 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 برای ممیز شناور دقت مضاعف.

این دستورها یک عدد ثابت را به‌عنوان عملوند سوم می‌گیرند. این عدد حالت گرد کردن را مشخص می‌کند:

عدد حالت
۰ نزدیک‌ترین
۱ کف (پایین)
۲ سقف (بالا)
۳ قطع کردن

برای نمونه:

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 در صورتی که یکی از دو عدد ممیز شناور مقدار NaN داشته باشد، parity flag (PF) را تنظیم می‌کند، در حالی که 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 (۲۵٪).

Note

این‌ها پسوندهای دستورهای ممیز شناور هستند:

پسوند اندازه‌ی عملوند
ss ۳۲ بیتی
sd ۶۴ بیتی

این‌ها برخی از دستورهایی هستند که در این مفهوم به آن‌ها اشاره شده است:

دستور توضیح
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 مقایسه انجام می‌دهد؛ اگر NaN باشد، PF را تنظیم می‌کند

این‌ها اعداد کنترل گرد کردن در roundss و roundsd هستند:

عدد حالت
0 نزدیک‌ترین
1 کف (پایین)
2 سقف (بالا)
3 برش

1. نرخ روزانه را با داشتن نرخ ساعتی محاسبه کنید

تابعی به اسم daily_rate پیاده‌سازی کنید تا نرخ روزانه را محاسبه کند؛ این تابع یک نرخ ساعتی را به‌عنوان پارامتر می‌گیرد که یک عدد ممیز شناور ۶۴ بیتی است. قرارداد تعیین می‌کند که هر روز ۸ ساعت قابل صورت‌حساب دارد.

daily_rate(60)
// => 480.0

نرخ روزانه‌ی برگردانده‌شده باید یک عدد ممیز شناور ۶۴ بیتی باشد.

2. قیمت تخفیف‌خورده را محاسبه کنید

تابعی به اسم apply_discount را پیاده‌سازی کنید تا قیمت را پس از تخفیف محاسبه کند. این تابع باید دو پارامتر بپذیرد: قیمت اصلی و نرخ تخفیف به درصد، که هر دو به‌صورت یک عدد ممیز شناور ۶۴ بیتی هستند.

apply_discount(150.0, 10.0)
// => 135.0

مقدار برگردانده‌شده باید همیشه یک عدد ممیز شناور ۶۴ بیتی باشد.

3. نرخ ماهانه را با داشتن نرخ ساعتی و تخفیف محاسبه کنید

تابعی به اسم monthly_rate را پیاده‌سازی کنید تا نرخ ماهانه‌ی تخفیف‌خورده را محاسبه کند. این تابع دو پارامتر می‌گیرد: یک نرخ ساعتی و تخفیف به درصد، که هر دو به‌صورت یک عدد ممیز شناور ۶۴ بیتی هستند.

monthly_rate(77.0, 10.5)
// => 12130

نرخ ماهانه‌ی برگردانده‌شده باید به نزدیک‌ترین عدد صحیح ۶۴ بیتی، به سمت بالا (سقف)، گرد شود.

4. تعداد روزهای کاری کامل را با داشتن بودجه، نرخ ساعتی و تخفیف محاسبه کنید

تابعی به اسم days_in_budget را پیاده‌سازی کنید که این پارامترها را می‌گیرد:

  1. یک بودجه، به‌صورت یک عدد صحیح بدون علامت ۶۴ بیتی.
  2. یک نرخ ساعتی، به‌صورت یک عدد ممیز شناور ۶۴ بیتی.
  3. یک تخفیف به درصد، به‌صورت یک عدد ممیز شناور ۶۴ بیتی.

این تابع باید تعداد روزهای کاری کاملی را که بودجه پوشش می‌دهد برگرداند.

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

تعداد روزهای برگردانده‌شده باید به نزدیک‌ترین عدد صحیح ۳۲ بیتی، به سمت پایین (کف)، گرد شود.

ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
x86-64 Assembly Exercism

آماده‌اید نرخ‌های فریلنسر را شروع کنید؟

در Exercism ثبت‌نام کنید تا x86-64 Assembly را همراه با 22 مفهوم130 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.