تبادل العملات

تبادل العملات

تمرين تعلّمي

مقدمة

ذُكر في المفاهيم السابقة أن التوجيه global يجعل دالة أو متغيرًا معرَّفًا في ملف مصدري واحد مرئيًا للملفات الأخرى. وبالمثل، يُخبر التوجيه extern المُجمِّع بأن دالة أو متغيرًا مستخدمًا في الملف المصدري الحالي معرَّف في ملف آخر.

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

بالنسبة لهذين التوجيهين، لا فرق إن كان الكود الخارجي معرَّفًا أو مستخدمًا في لغة التجميع أو في لغة عالية المستوى. لذا، على سبيل المثال، يمكن استخدام حتى دالة معرَّفة في لغة عالية المستوى في كود x86-64 إذا أُعلن أنها extern.

وبالطريقة نفسها، فإن اصطلاح الاستدعاء مشترك بين كل تلك الدوال، سواء عُرِّفت بلغة التجميع أو بلغات عالية المستوى، وحتى مع نظام التشغيل.

ومع ذلك، بما أن البيانات في لغة التجميع مجرد تسلسل من البايتات، فمن المهم أن تعرف كيف تدير اللغة عالية المستوى التي تتعامل معها الذاكرة.

أنواع C

لغة C خيار شائع كلغة عالية المستوى للتفاعل مع كود التجميع. فهي تسهّل مهمة التعامل مع نظام التشغيل، إذ توفر أغلفة عالية المستوى لكثير من المهام المهمة، مثل الطباعة على الشاشة، والقراءة من لوحة المفاتيح، أو تخصيص ذاكرة ديناميكية.

في هذا المسار، تُكتب الاختبارات بلغة C، لذا من المهم تقديم نظرة عامة على اللغة.

Note

يمكن إضافة المؤهِّل const إلى أي من الأنواع المذكورة أدناه. وهذا يجعلها للقراءة فقط.

الأنواع الأولية

توجد أنواع أولية كثيرة في C، وقد يختلف حجمها بالبايتات. ويلخّص الجدول التالي بعضها، مع حجمها المعتاد في نظام x86-64:

النوع عدد البايتات عدد صحيح / عدد عشري
_Bool 1 عدد صحيح
char 1 عدد صحيح
short 2 عدد صحيح
int 4 عدد صحيح
long 4 أو 8 عدد صحيح
long long 8 عدد صحيح
float 4 عدد عشري
double 8 عدد عشري

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

توجد أسماء بديلة لأنواع الأعداد الصحيحة، مُعرَّفة لتوفير تحكّم دقيق في حجمها:

الاسم البديل عدد البايتات
int8_t 1
int16_t 2
int32_t 4
int64_t 8

ويمكن إضافة البادئة u إلى تلك الأسماء البديلة للإشارة إلى نوع غير مُوقَّع، على سبيل المثال: uint64_t.

ومن الأسماء البديلة الأخرى الجدير بالذكر:

الاسم البديل النوع
bool _Bool
size_t عادةً عدد صحيح غير مُوقَّع بحجم 8 بايت

يتميّز _Bool، أو اسمه البديل bool، بأنه لا يمكنه أن يأخذ إلا إحدى قيمتين: true (1) وfalse (0)، على الرغم من أنه يشغل 1 بايت كامل.

وتُمرَّر قيم هذه الأنواع الأولية إلى الدوال وتُرجَع منها وفق القواعد المعتادة للأعداد الصحيحة وقيم الأعداد العشرية.

التعدادات

enum هو نوع يمكنه أن يأخذ واحدًا من عدد من العناصر المحددة صراحةً:

enum example {
    example_1,
    example_2,
    example_3,
    example_4
};

في C، تُحوَّل قيم التعداد ضمنيًا إلى نوع صحيح، وهو عادةً int، أي عدد صحيح بحجم 4 بايت.

افتراضيًا، يُحوَّل أول عنصر ممكن في التعداد إلى 0، وتُحوَّل كل العناصر التالية إلى العدد الصحيح الذي يليه. لذا، في enum example المعرَّف أعلاه، سيكون للعنصر example_1 القيمة 0، وللعنصر example_2 القيمة 1، وهكذا.

ويمكن تحديد قيمة لأي عنصر في التعداد، وعندئذٍ يأخذ العنصر التالي افتراضيًا العدد الصحيح الذي يليه كقيمة:

enum example_2 {
    example_1 = 4,
    example_2 = 8,
    example_3
}; // example_3 is equal to 9

عناوين الذاكرة

في C، يُشار إلى عنوان الذاكرة بواسطة مؤشر إلى نوع، ويُرمز إليه بالعامل *. لذا، على سبيل المثال، يشير int64_t * إلى موقع في الذاكرة يحوي واحدًا أو أكثر من int64_t.

وتُعامَل العناوين كأعداد صحيحة بحجم 8 بايت، كما هو معتاد.

المصفوفات

تُمرَّر المصفوفات إلى الدوال وتُرجَع منها كمؤشرات إلى عنصرها الأول.

int64_t example_arr[] = {1, 2, 3}; // this is an array of 3 signed 8-byte integers
fn(example_arr); // this is a function that passes a pointer (int64_t *) to the beginning of the array as an argument

حجم كل عنصر في المصفوفة هو حجم نوع عناصر المصفوفة. فالمصفوفة example_arr المعرَّفة أعلاه، على سبيل المثال، تحتوي على 3 عناصر بحجم 8 بايت لكل عنصر، أي 24 بايت في المجموع.

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

السلاسل النصية

السلاسل النصية في C هي مصفوفة من char، وهو نوع بحجم 1 بايت. ومعظم السلاسل النصية مكوَّنة من محارف ASCII وتنتهي بـ NUL، أي أنها تنتهي عند العثور على بايت قيمته 0. وهذا يعني أنه لا حاجة عادةً إلى تمرير طول السلسلة النصية كوسيط منفصل.

التعليمات

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

وهذه هي مواصفاته للتطبيق.

Note

الدوال في هذا التمرين معلنة في ملف مصدري بلغة C بالتوقيعات المرفقة. يجب أن يتبع كود x86-64-assembly الخاص بك هذه المواصفات.

1. تتبّع العملات المختلفة

لقد أنشأت enum currency_t لتتبّع العملات المختلفة التي يستخدمها تشاندلر في رحلاته:

enum currency_t {
    GBP,
    EUR,
    JPY,
    AUD,
    BRL,
    CNY,
    CAD,
    INR
};

تحتاج أحيانًا إلى طباعة هذه العملات على الشاشة لتتحقق مما إذا كانت صحيحة.

أنشئ الدالة stringify_currency:

void stringify_currency(char *buffer, enum currency_t currency);

لا تُرجع هذه الدالة أي قيمة، وينبغي أن تخزّن سلسلة نصية بأسلوب C في الموقع الذي يشير إليه buffer. ينبغي أن تمثّل السلسلة النصية بصريًا نوع العملة التي مُرّرت إلى الدالة:

char buffer[4];
stringify_currency(buffer, CAD);
// => "CAD"

2. تحقّق من سعر الصرف

أنشئ الدالة exchange_rate:

double exchange_rate(enum currency_t domestic_currency, enum currency_t foreign_currency, const double *value_in_US_dollars);

يمثّل الوسيط domestic_currency عملة تشاندلر المحلية التي ستُصرف مقابل foreign_currency.

value_in_US_dollars هو مؤشر إلى مصفوفة من النوع double تحتوي على قيمة وحدة واحدة من كل عملة بالدولار الأمريكي. العملات هي فهارس في هذه المصفوفة. فإذا كان EUR عنصرًا صالحًا في enum currency_t، فإن value_in_US_dollars[EUR] يعطي قيمة 1 EUR بالدولار الأمريكي.

ينبغي أن تُرجع هذه الدالة قيمة وحدة واحدة من العملة الأجنبية بالعملة المحلية.

const double value_in_US_dollars[8] = {
    1.33329, 1.16443, 0.00654374, 0.650888,
    0.185577, 0.140420, 0.714142, 0.0113863
};

exchange_rate(JPY, GBP, value_in_US_dollars);
// => 203.750454633

3. احسب قيمة الأوراق النقدية

أنشئ الدالة get_value_of_bills:

uint64_t get_value_of_bills(unsigned long long denomination, unsigned short number_of_bills);

الوسيط denomination هو قيمة ورقة نقدية واحدة، أما number_of_bills فهو العدد الإجمالي للأوراق النقدية.

لا يتعامل كشك الصرف هذا إلا مع نقود نقدية بمضاعفات محددة. يجب أن يكون الإجمالي الذي تحصل عليه قابلًا للقسمة على قيمة "ورقة" واحدة أو وحدة واحدة، وقد يتبقى كسر أو باقٍ. ينبغي أن تُرجع دالتك القيمة الإجمالية للأوراق النقدية فقط (باستثناء المبالغ الكسرية) التي سيعيدها الكشك. ولسوء الحظ، يحتفظ الكشك بالباقي/الفكة كمكافأة إضافية.

get_value_of_bills(5, 128);
// => 640

4. احسب عدد الأوراق النقدية

أنشئ الدالة get_number_of_bills:

unsigned int get_number_of_bills(float amount, unsigned long long denomination);

ينبغي أن تُرجع هذه الدالة عدد الأوراق النقدية التي يمكنك الحصول عليها ضمن المبلغ المعطى. بعبارة أخرى: كم عدد الأوراق النقدية الكاملة التي تتسع لها قيمة المبلغ المبدئي؟ تذكّر أنك لا تستطيع الحصول إلا على أوراق نقدية كاملة، لا أجزاء من الأوراق، لذا تذكّر أن تقسم على هذا الأساس. أنت في الواقع تقرّب للأسفل إلى أقرب ورقة/فئة نقدية كاملة.

get_number_of_bills(127.5, 5);
// => 25

5. احسب القيمة بعد الصرف

أنشئ الدالة exchangeable_value:

uint32_t exchangeable_value(float budget, double exchange_rate, uint8_t spread, unsigned long long denomination);

المعامل spread هو النسبة المئوية المقتطعة كرسوم صرف، مكتوبة كعدد صحيح. يجب تحويله إلى قيمة عشرية بقسمته على 100. لاحظ أن الهامش يُضاف إلى سعر الصرف، بحيث يؤدي هامش قدره 10 إلى سعر صرف أعلى بنسبة 10%.

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

exchangeable_value(127.25, 0,873350884, 10, 20);
// => 120
exchangeable_value(127.25, 0,873350884, 10, 5);
// => 130
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
x86-64 Assembly Exercism

مستعد لبدء تبادل العملات؟

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