Készletkezelés

Készletkezelés

Tanulófeladat

Bevezetés

Egész számok

Bináris jelölés

Az egész szám egy absztrakció, amely egész számokat reprezentál, például 4, -2, 0 vagy 64532.

Ahhoz, hogy egy egész számot bájtok sorozataként ábrázoljunk, a bináris jelölést használjuk. Ebben a jelölésben a sorozat minden bitje a kettő egy-egy hatványát reprezentálja, és az érték növekszik, ahogy a bit indexe jobbról balra növekszik.

Előjel nélküli számok

Ha a szám csak nemnegatív lehet, előjel nélküli számnak nevezzük.

Az előjel nélküli számokat közvetlenül a sorozatukban beállított összes bithez tartozó kettőhatványok összegeként ábrázoljuk.

A regiszterben ábrázolható nemnegatív egész számok tartománya 0-tól (egyetlen bit sincs beállítva) 2⁶⁴ - 1-ig (mind a 64 bit be van állítva) terjed.

Egy előjel nélküli szám nagyobb méretre szélesítése úgy történik, hogy az összes felső bitet 0-val töltjük fel, így egyetlen új bit sem járul hozzá az értékhez. Ezt nulla-kiterjesztésnek nevezzük.

A movzx utasítás (z a nullát jelöli) nulla-kiterjeszti a 8 bites vagy 16 bites forrásoperandust egy nagyobb céloperandusra. A 32 bites forrásoperandust egy egyszerű mov mindig nulla-kiterjeszti a céloperandus mind a 64 bitjére.

Előjeles számok

Ha egy egész szám pozitív vagy negatív értéket vehet fel, előjeles számnak nevezzük.

A negatív számok ábrázolására az x86-64 a kettes komplemens reprezentációt használja.

A kettes komplemensben az előjeles számokat szintén a beállított bitekhez tartozó kettőhatványok összegeként ábrázoljuk. Ha azonban a legfelső bit be van állítva, akkor azt a többihez képest nem hozzáadjuk, hanem kivonjuk.

Mivel ez a bit nagyobb értéknek felel meg, mint az összes többi összege, a gyakorlatban ez azt jelenti, hogy az ilyen bitet beállított szám mindig negatív. Ezt a különleges bitet előjelbitnek nevezzük.

Egy előjeles szám nagyobb méretre szélesítése azt jelenti, hogy minden új felső bitet az előjelbit másolatával töltünk fel, így az érték megmarad. Ezt előjel-kiterjesztésnek nevezzük.

A movsx utasítás (s az előjelet jelöli) előjel-kiterjeszti a 8 bites vagy 16 bites forrásoperandust egy nagyobb céloperandusra. A movsx movsxd nevű változata ugyanezt teszi 32 bites forrásoperandusról 64 bites céloperandusra.

A neg utasítással megváltoztathatod egy szám előjelét.

Caution

Assemblyben nincs mód arra, hogy megmondjuk, egy bájtsorozat előjeles vagy előjel nélküli számot reprezentál-e. A programozó felelőssége, hogy jelentést adjon ezeknek a bájtoknak.

A megjegyzések használata nagy segítség lehet ebben a feladatban.

Azonnali értékek

Egy korábbi fogalomban említettük, hogy egy állandó szám, például 4 vagy -15, forrásoperandusként használható sok utasításnál. Ezeket a számokat azonnali értékeknek nevezzük.

Az azonnali értéket nem regiszterben vagy memóriában tároljuk: magában az utasításban van kódolva. A legtöbb utasításban a számára fenntartott hely csak 32 bit széles, függetlenül attól, mekkora a céloperandus.

Amikor a céloperandus 64 bit széles, ezt a 32 bitet előjel-kiterjesztjük, hogy kitöltse. Az operandus felső fele teljesen megtelik az azonnali érték legfelső bitjének másolataival, így csak egy 32 bites előjeles egész szám tartományába eső szám írható le:

add rax, -1          ; the immediate is sign-extended, so all 64 bits of rax are affected
add rax, 2147483647  ; the largest immediate an instruction like this accepts

Az ezen a tartományon kívül eső szám nem használható azonnali értékként. Kivétel ez alól a mov, amely teljes 64 bites azonnali értéket is fogadhat, ha a céloperandus regiszter. Ha 64 bites azonnali értékre van szükség, először a mov utasítással töltsd be egy regiszterbe, majd használd azt a regisztert:

mov rax, 3435973837           ; this works, mov can take a 64-bit immediate
mov rdx, 18446744073709551615 ; the largest immediate mov accepts
sub rdx, rax

Vedd figyelembe, hogy egy negatív azonnali érték és az ugyanolyan bitreprezentációjú előjel nélküli szám egyenértékű, és pontosan ugyanarra az értékre fordul:

mov rax, -1                   ; rax = 18446744073709551615
mov rax, 18446744073709551615 ; rax = -1

Összeadás

Két szám összeadása az add utasítással számítható ki.

Van egy inc egyoperandusú utasítás is, amely 1-et ad hozzá az operandusában lévő értékhez:

inc rax ; rax = rax + 1

Két egész szám összeadása ugyanúgy működik előjel nélküli és előjeles számok esetén is.

Kivonás

Két egész szám kivonása a sub utasítással történik.

Van egy dec egyoperandusú utasítás is, amely 1-et von ki az operandusában lévő értékből:

dec rax ; rax = rax - 1

Két egész szám kivonása szintén ugyanúgy működik előjel nélküli és előjeles számok esetén is.

Szorzás

Két különböző utasítás létezik két szám szorzására x86-64-en. Főszabályként az előjel nélküli szorzás a mul, míg az előjeles szorzás az imul utasítást használja.

A mul utasítás a következő egyoperandusú formát veszi fel, ahol src a forrásoperandus:

mul src

Az imul utasítás egyoperandusú, kétoperandusú vagy háromoperandusú formát vehet fel:

imul src
imul dest, src
imul dest, src1, src2
Egyoperandusú szorzás

Egyoperandusú formában a szorzás végrehajtásához két regisztert használunk implicit módon: rax és rdx. Ha a szorzás két 64 bites számot érint, az eredmény alsó 64 bitje rax-ben, a felső 64 bitje pedig rdx-ben lesz.

Ezt általában rdx:rax-nek nevezik, jelezve, hogy mindkét regiszter együttműködve használatos:

mul rcx ; rax = lower 64 bits of rax * rcx
        ; rdx = upper 64 bits of rax * rcx

Ugyanez történik más operandusméreteknél is. Így például ha két 32 bites számot szorzunk, eax-et és edx-et használjuk.

Kivétel a két bájt közötti szorzás.

Ebben az esetben dl:al helyett ax-et használunk. Az ax alsó része (al) kapja a szorzat alsó 8 bitjét, a felső része (ah) pedig a felső 8 bitet.

Caution

A szorzásban implicit módon használt regiszterek, például rax és rdx, mindig felülíródnak. Ha ezekre a regiszterekre később szükség lesz, az értékeiket a művelet előtt mentsd el.

Kétoperandusú szorzás

Az imul kétoperandusú formájának van egy explicit céloperandusa, és a szokásos szintaxist követi. A rdx nem használatos. Ehelyett az eredményt levágjuk, hogy elférjen a céloperandusban.

imul r8, r9 ; r8 = lower 64 bits of r8 * r9
Háromoperandusú szorzás

Az imul háromoperandusú formájának két forrásoperandusa van, amelyek közül a második mindig egy azonnali érték (állandó szám). Mindkét forrásoperandust megszorozzuk, az eredményt levágjuk, és a céloperandusba helyezzük:

imul r8, r9, 100 ; r8 = lower 64 bits of r9 * 100

Vedd figyelembe, hogy a céloperandus nem vesz részt a szorzásban. Csupán fogadja az eredményt.

Túlcsordulás kezelése

A kétoperandusú és a háromoperandusú szorzás is levágja az eredményt, hogy elférjen a céloperandus méretében. Az egyoperandusú szorzás megőrzi a teljes tartományt, de az eredmény általában két regiszterre oszlik, rdx-re és rax-re.

Ezért néha hasznos az operandusokat a szorzás előtt kiszélesíteni, hogy a teljes szorzat elférjen egyetlen regiszterben. Az előjel nélküli operandus nulla-kiterjesztést kap, az előjeles pedig előjel-kiterjesztést:

movzx eax, di ; di and si hold unsigned 16-bit numbers
movzx ecx, si
mul ecx       ; the 32-bit product fits in eax, and edx is cleared

Osztás

Ahogy a szorzásnál, úgy az osztásnál is két utasítás létezik két szám közötti osztás elvégzésére. Az előjel nélküli osztás a div, míg az előjeles osztás az idiv utasítást használja.

Mindkét utasítás csak egy operandussal működik:

div src
idiv src

A 16 bites, 32 bites és 64 bites osztás osztalékként dx:ax, edx:eax, illetve rdx:rax értéket használ. Ezekben az esetekben mindkét regiszter együttműködve hoz létre egy 2N bites értéket, ahol N a művelet mérete (16 bit, 32 bit vagy 64 bit). Ezt az értéket elosztjuk a forrásoperandussal. A hányados az ax, eax vagy rax regiszterbe kerül, a maradék pedig a dx, edx vagy rdx regiszterbe, a művelet méretének megfelelően.

A bájtok közötti osztás különleges: dl:al helyett ax-et használunk. Az ax alsó 8 bitje (al) kapja a művelet hányadosát, a felső 8 bitje (ah) pedig a maradékot.

Vedd figyelembe, hogy az osztalék minden bitjét megfelelően be kell állítani az osztás előtt. Az rdx-ben (vagy 8 bites osztásnál ah-ban) beállított bármely bit hozzájárul az osztandó értékhez.

Előjel nélküli osztásnál, amikor az osztandó érték elfér az alsó felében, a felső felét törölni kell. Bármely utasítás megteszi, amely törli ezeket a biteket. Például a mov edx, 0 törli a felső biteket 32 bites osztásnál.

Előjeles osztásnál ehelyett előjel-kiterjesztést kell alkalmazni. Vannak utasítások, amelyek automatizálják ezt a folyamatot: cbw, cwd, cdq és cqo. Az első beállítja az ah bitjeit az al előjele szerint. A többiek előjel-kiterjesztést végeznek ax-ből dx-be, eax-ből edx-be, illetve rax-ből rdx-be.

Caution

Az osztásban implicit módon használt regiszterek, például rax és rdx, mindig felülíródnak. Ha ezekre a regiszterekre később szükség lesz, az értékeiket az osztás előtt mentsd el.

Utasítások

Egy helyi üzlet nagyobb raktárba költözteti a készletét. Téged bíztak meg azzal, hogy csomagolj be és szállíts el mindent.

Négy részfeladatod van, mindegyik a szállítás megszervezéséhez kapcsolódik.

Note

Ezek az ebben a fogalomban említett utasítások:

Utasítás Leírás
add a, b a = a + b
inc a a = a + 1
sub a, b a = a - b
dec a a = a - 1
imul a rdx:rax = a * rax (előjeles)
imul a, b a = a * b (előjeles, csonkolt)
imul a, b, c a = b * c (előjeles, csonkolt)
mul a rdx:rax = a * rax (előjel nélküli)
div a rax = hányados, rdx = rdx:rax / a maradéka (előjel nélküli)
idiv a rax = hányados, rdx = rdx:rax / a maradéka (előjeles)
movzx a, b a = b, a plusz bitekhez 0-t adva
movsx a, b a = b, a plusz bitekhez 1-et adva, ha b < 0, egyébként 0-t
Note

Ne feledd, hogy ugyanahhoz a regiszterhez különböző méretekkel is hozzáférhetsz, ha megváltoztatod az operandus nevét. Például: rax (64 bites), eax (32 bites), ax (16 bites), al (8 bites).

A teljes táblázathoz lapozd fel az előző fogalmat.

1. Határozd meg az egyes dobozok súlyát

A termékeket dobozokba csomagolják, amelyeket a súlyukkal kell felcímkézni. Nincs a közelben mérleg, de szerencsére tudod, hogy egy-egy termék átlagosan mennyit nyom.

A könnyebb rendszerezés érdekében egy dobozban csak két különböző termék darabjai lehetnek.

Definiálj egy get_box_weight függvényt, amely visszaadja egy doboz összsúlyát g-ban. A függvény a következő paramétereket veszi át, ebben a sorrendben:

  • Az első termék darabszámát a dobozban
  • Az első termék egy darabjának súlyát g-ban
  • A második termék darabszámát a dobozban
  • A második termék egy darabjának súlyát g-ban

Vedd figyelembe, hogy egy üres doboz súlya 500 g. A WEIGHT_OF_EMPTY_BOX konstans a megoldásfájl tetején van definiálva.

Példa:

get_box_weight(30, 40, 50, 20);
// => 2700

Minden argumentum 16 bites nemnegatív egész szám, a visszatérési érték pedig egy 32 bites nemnegatív egész szám.

2. Számold ki, hány doboz fér be a teherautóba

A dobozokat egymásra rakják, és teherautóval szállítják az új raktárba. A teherautóban viszont véges a függőleges hely.

Definiálj egy max_number_of_boxes függvényt, amely visszaadja, hogy egy adott magasságú dobozból hány fér el egymásra rakva a teherautóban.

A függvény paraméterként a doboz magasságát kapja meg cm-ben. Vedd figyelembe, hogy a teherautó belső magassága 300 cm. A TRUCK_HEIGHT konstans a megoldásfájl tetején van definiálva.

Példa:

max_number_of_boxes(30);
// => 10

Az argumentum és a visszatérési érték is 8 bites nemnegatív egész szám. A doboz magassága mindig legalább 2, így az eredmény elfér 8 biten.

3. Ellenőrizd, hogy minden termék számba van-e véve

Az új raktárban van egy ellenőrzőlista, amelyen termékenként fel van tüntetve, hány darab vár még a számbavételre. Minden oda beszállított új doboz esetében ki kell számolnod a dobozban lévő minden termék új értékét az ellenőrzőlistán.

Definiálj egy items_to_be_moved függvényt, amely visszaadja, hogy egy adott termékből hány darabot kell még az új raktárba szállítani. A függvény a következő paramétereket veszi át, ebben a sorrendben:

  • Egy termék még számba nem vett darabszámát
  • A termék darabszámát egy dobozban

Példa:

items_to_be_moved(76532, 120);
// => 76412

Az argumentumok 32 bites nemnegatív egész számok. A visszatérési érték egy 32 bites egész szám. Ha a folyamat során hiba történik, előfordulhat, hogy az eredmény negatív szám.

4. Vedd át a fizetést

A fizetésed attól függ, hány dobozt szállítottál el, és hány fuvarra volt szükség. Minden dobozért 5 dollárt kapsz, és minden fuvarért 220 dollárt kapsz. A PAY_PER_BOX és a PAY_PER_TRUCK_TRIP konstans a megoldásfájl tetején van definiálva.

Ne feledd, hogy a fizetés egy részét előre megkaphattad a kezdeti költségek fedezésére, és ezt az előre kifizetett összeget le kell vonni a végső fizetésből. Emellett néhány termékre nincs biztosítás, és a fizetésedet csökkenti az ilyen termékekből összetört vagy elveszett darabok értéke is. Ha nem figyelsz, akár még tartozhatsz is!

Ez azt jelenti, hogy a nettó összeg, amit kapnod kell, vagy amivel tartozol:

net = boxes * PAY_PER_BOX + trips * PAY_PER_TRUCK_TRIP - up_front - broken_items * item_value

Ezt a fizetést, vagy tartozást, egyenlően osztjátok el te és a felbérelt munkások között. A megmaradó pénz, vagy tartozás, a tiéd. Például ha a nettó összeg 100, amelyet 6 ember osztozik meg (te és 5 munkás), akkor 20-at kapsz (100/(5 + 1) = 16, plusz a megmaradó 4).

Definiálj egy calculate_payment függvényt, amely visszaadja, hogy a végén mennyit kell kapnod, vagy fizetned. A függvény a következő paramétereket veszi át, ebben a sorrendben:

  • Hogy előre mennyit kaptál meg, 64 bites nemnegatív egész számként
  • Az elszállított dobozok teljes számát, 32 bites nemnegatív egész számként
  • A megtett fuvarok számát, 32 bites nemnegatív egész számként
  • Az összetört vagy elveszett darabok számát, 32 bites nemnegatív egész számként
  • Minden elveszett darab értékét, 64 bites nemnegatív egész számként
  • A veled a fizetést vagy tartozást megosztó munkások számát, 8 bites pozitív egész számként

Példa:

calculate_payment(2000, 1000, 5, 21, 2, 1);
// => 2029

A visszatérési érték egy 64 bites egész szám.

Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg
x86-64 Assembly Exercism

Készen állsz elkezdeni a(z) Készletkezelés feladatot?

Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) x86-64 Assembly nyelvet 22 fogalom130 feladat segítségével, valódi emberi mentorálással, mindez ingyen.