text
, zdrojový balík ve složkách 00
(organizační
informace), 01
až 12
(jednotlivé kapitoly = týdny semestru),
dále s1
až s3
(sady úloh) a konečně ve složce sol
vzorová
řešení. Doporučujeme soubory stahovat dávkově pomocí volby
„stáhnout jako ZIP“.
aisa
(buď za pomoci ssh
nebo putty
) zadáním příkazu pb111 update
. Všechny výše
uvedené složky pak naleznete ve složce ~/pb111
.
bl. | téma | |
1 | 1. | výpočetní stroj |
2. | lokální proměnné, řízení toku | |
3. | podprogram | |
4. | adresa, ukazatel | |
2 | 5. | pole, index, ukazatel do pole |
6. | struktura, zřetězený seznam | |
7. | základy alokace paměti | |
8. | TBD | |
3 | 9. | dynamické pole |
10. | slovník a množina | |
11. | správa paměti | |
12. | TBD |
únor | |||||||
Po | Út | St | Čt | Pá | So | Ne | |
#1 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
cv 0 | 01/v | 01/p | |||||
#2 | 24 | 25 | 26 | 27 | 28 | ||
cv 1 | s1/1 | s1/2 | 02/v | s1/3 |
březen | |||||||
Po | Út | St | Čt | Pá | So | Ne | |
#2 | 1 | 2 | |||||
02/p | |||||||
#3 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
cv 2 | s1/4 | s1/5 | 03/v | s1/6 | 03/p | ||
#4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
cv 3 | s1/7 | s1/8 | 04/v | s1/9 | 04/p | ||
#5 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
cv 4 | s1/10 | s1/11 | 05/v | s1/12 | 05/p | ||
#6 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
cv 5 | s2/1 | s2/2 | 06/v | s2/3 | 06/p | ||
#7 | 31 | ||||||
cv 6 | s2/4 |
duben | |||||||
Po | Út | St | Čt | Pá | So | Ne | |
#7 | 1 | 2 | 3 | 4 | 5 | 6 | |
s2/5 | 07/v | s2/6 | 07/p | ||||
#8 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
cv 7 | s2/7 | s2/8 | 08/v | s2/9 | 08/p | ||
#9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
cv 8 | s2/10 | s2/11 | 09/v | s2/12 | 09/p | ||
#10 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
cv 9 | s3/1 | s3/2 | 10/v | s3/3 | 10/p | ||
#11 | 28 | 29 | 30 | ||||
cv10 | s3/4 | s3/5 |
květen | |||||||
Po | Út | St | Čt | Pá | So | Ne | |
#11 | 1 sv | 2 | 3 | 4 | |||
11/v | s3/6 | 11/p | |||||
#12 | 5 | 6 | 7 | 8 sv | 9 | 10 | 11 |
cv11 | s3/7 | s3/8 | 12/v | s3/9 | 12/p | ||
#13 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
cv12 | s3/10 | s3/11 | s3/12 | ||||
19 | 20 | 21 | 22 | 23 | 24 | 25 | |
26 | 27 | 28 | 29 | 30 | 31 | ||
známka | předběžné minimum | po vyhodnocení semestru |
A | 360 | 90. percentil + 60 |
B | 320 | 65. percentil + 60 |
C | 280 | 35. percentil + 60 |
D | 240 | 10. percentil + 60 |
E | 200 | 200 |
NN
v ISu (např. 01
),
pb111 submit
ve složce ~/pb111/NN
.
00/t*
).
r
z aktuální
kapitoly (které konkrétní příklady budete ve cvičení řešit vybere
cvičící, může ale samozřejmě vzít v potaz Vaše preference):
sN_úkol
v ISu (např. s1_a_queens
),
pb111 submit sN_úkol
ve složce ~/pb111/sN
, např.
pb111 submit s1_a_queens
.
pb111 status
.
e
a r
) a skripta,
tiny
a odpovídající virtuální stroj.
p
/r
/v
ze sbírky.
tokens.txt
, kde
naleznete 3 kódy. Každý z nich lze použít nejvýše jednou (vložením
do komentáře do jednoho z příkladů), a každé použití kódu odhalí
výsledek verity testu pro ten soubor, do kterého byl vložen. Toto se
projeví pouze při prvním odevzdání s vloženým kódem, v dalších
odevzdáních bude tento kód ignorován (bez ohledu na soubor, do
kterého bude vložen).
rv
(return value),
l1
až l7
(local),
t1
až t6
(temporary),
bp
a sp
,
pc
(program counter),
pc
a sp
– všechny
ostatní jsou z pohledu stroje ekvivalentní a jejich jména nemají pro
samotný výpočet žádný speciální význam – jedná se pouze o konvenci,
která nám usnadní čtení (a psaní) programů.
pc
se načtou dvě šestnáctibitová
slova – hi
z adresy pc
a lo
z adresy pc + 2
– která
kódují jednu instrukci,
hi
kóduje operaci (vyšší slabika), cílový registr a
první registrový operand,
lo
kóduje přímý (immediate) operand, nebo druhý
registrový operand (v nejvyšší půlslabice),
pc
,
rv
je
registr číslo 0 a sp
je registr číslo 15. Je vidět, že číslo
registru lze zakódovat do jedné půlslabiky (registr pc
operandem
být nemůže).
push
, pop
),
put
),
copy
),
sext
),
ld
, ldb
),
st
, stb
),
add
, sub
),
mul
,
sdiv
, smod
),
udiv
a umod
),
eq
, ne
),
slt
, sgt
),
sle
, sge
),
ult
, ugt
), a konečně
ule
, uge
),
and
, or
a xor
aplikované po bitech,
shl
(levý), shr
(pravý) a aritmetický sar
,
jmp
,
jz
(jump if zero) a jnz
(if not zero),
call
, ret
),
halt
zastaví výpočet,
asrt
zastaví výpočet s chybou, je-li operand nulový.
put 0 → rv ; vynuluj registr rv
add 1, rv → rv ; do registru rv přičti 1
jnz rv, 0x0004 ; je-li rv nenulové, skoč na adresu 4
add
) je 4 (její kódování je uloženo na adresách
4, 5, 6 a 7). Program jak je napsaný provede prázdný cyklus 65535×
(v poslední iteraci je v registru rv
hodnota ffff
, přičtením
jedničky se změní na nulu, podmíněný skok „není-li rv
nula“ se
neprovede a cyklus tak skončí).
put 0 → l1 ; vynuluj registr l1
put 0 → rv ; vynuluj registr rv
add 1, rv → rv ; do registru rv přičti 1
jnz rv, 0x0004 ; je-li rv nenulové, skoč na adresu 4
jnz
neodpovídá
původnímu programu – tento nový program bude cyklit donekonečna
(rozmyslete si proč).
put 0 → rv ; vynuluj registr rv
loop: ; návěstí pro první instrukci cyklu
add 1, rv → rv ; do registru rv přičti 1
jnz rv, loop ; je-li rv nenulové, skoč na začátek cyklu
int
a unsigned
je konkrétní rozsah přípustných hodnot daný implementací – na mnoha systémech jsou tyto typy 32bitové.
tinyvm
]step
, která načte, dekóduje
a provede jednu instrukci. Vstupními parametry jsou:
pc
je aktuální hodnota programového čítače,
regs
je seznam 16 celých čísel, každé v rozsahu 0 až 65535,
jenž reprezentují hodnoty uložené v registrech,
mem
je seznam 65536 celých čísel, každé v rozsahu 0 až 255,
přitom číslo uložené na indexu i
reprezentuje paměťovou
buňku s adresou i
.
def step( pc: int, regs: list[ int ],
mem: list[ int ] ) -> tuple[ int, int ]:
assert 0 <= pc < 65536
assert len( regs ) == 16
assert len( mem ) == 65536
opcode
je číslo
operace, složené ze dvou půlslabik – kategorie cat
a
konkrétní operace z dané kategorie op
. V šestnáctkovém
zápisu tedy opcode = 0x12
značí operaci 2 z kategorie 1.
opcode = mem[pc]
cat = opcode // 16
op = opcode % 16
0x82
, je
výstupním registrem ten s číslem 8 (t1
) a (prvním) vstupním
registrem je registr číslo 2, totiž l2
.
r_out = mem[ pc + 1 ] // 16
r_1 = mem[ pc + 1 ] % 16
imm = mem[ pc + 3 ] + mem[ pc + 2 ] * 256
r_2 = mem[ pc + 2 ] // 16
addr = imm
print_state
nemá na výpočet
stroje žádný vliv, není tedy nutné ji blíže zkoumat. Můžete si
ji ale přizpůsobit dle vlastního vkusu nebo potřeby.
print_state( pc, regs, cat, op, imm, r_out, r_1, r_2 )
asrt
ukončí výpočet s chybou, je-li ve vstupním registru hodnota
nula, jinak pokračuje ve výpočtu další instrukcí. Instrukce
halt
výpočet zastaví vždy (nikoliv ale chybou).
if opcode == 0xee and regs[ r_1 ] == 0: # asrt
return pc, ERROR
if opcode == 0xef: # halt
return pc, HALT
copy
uloží do výstupního registru hodnotu registru vstupního,
operace put
uloží přímý operand do výstupního registru a
konečně sext
provede znaménkové rozšíření spodní slabiky
vstupního registru a výsledek uloží do toho výstupního.
if opcode == 0x0c: # copy
regs[ r_out ] = regs[ r_1 ]
if opcode == 0x0d: # put
regs[ r_out ] = imm
if opcode == 0x0e: # sext
regs[ r_out ] = as_signed( regs[ r_1 ], 8 ) % 65536
st
(store):
stb
zapíše pouze spodní slabiku vstupního registru
a přepíše tedy jedinou buňku paměti.
if opcode in [ 0x03, 0x04 ]: # st, stb
addr = ( addr + regs[ r_out ] ) % 65536
if opcode == 0x03: # stb
mem[ addr ] = regs[ r_1 ] % 256
if opcode == 0x04: # st
mem[ addr + 0 ] = regs[ r_1 ] // 256
mem[ addr + 1 ] = regs[ r_1 ] % 256
Operace ld
analogicky:
ldb
přečte z paměti pouze jednu slabiku a na celé
slovo ji doplní levostrannými nulami.
if opcode in [ 0x01, 0x02 ]: # ld, ldb
addr = ( addr + regs[ r_1 ] ) % 65536
if opcode == 0x01: # ldb
regs[ r_out ] = mem[ addr ]
if opcode == 0x02: # ld
regs[ r_out ] = mem[ addr ] * 256 + mem[ addr + 1 ]
Konečně jsou v kategorii 0 operace push
a pop
pro práci se
zásobníkem. Jejich efekt je implementován pomocnými
procedurami push
a pop
níže, protože stejný efekt budeme
potřebovat i při implementaci některých dalších operací.
push
sníží hodnotu uloženou v registru sp
o dvě a
na výslednou adresu uloží slovo ze vstupního registru.
pop
analogicky nejprve přečte slovo uložené na
adrese dané registrem sp
, uloží ho do výstupního registru a
konečně hodnotu registru sp
o dvě zvýší.
if opcode == 0x0a: # push
push( regs, mem, regs[ r_1 ] )
if opcode == 0x0b: # pop
regs[ r_out ] = pop( regs, mem )
call
uloží
hodnotu programového čítače na zásobník (podobně jako operace
push
) – jedná se o tzv. návratovou adresu. Dále nastaví pc
na hodnotu přímého operandu, čím předá řízení podprogramu na
této adrese uloženému.
if opcode == 0xfe: # call
push( regs, mem, pc )
return imm, CONT
ret
ukončí vykonávání podprogramu a řízení vrátí
volajícímu – návratovou adresu načte ze zásobníku podobně jako
operace pop
. Tuto adresu nezapomeneme zvýšit, protože adresa
na uložená zásobníku ukazuje na instrukci call
, která volání
způsobila.
if opcode == 0xff: # ret
return pop( regs, mem ) + 4, CONT
jmp
a podmíněné jz
a
jnz
– pouze nastaví programový čítač na hodnotu přímého
operandu. Podmíněný skok se provede v případě, že je hodnota
vstupního registru nulová (jz
) nebo naopak nenulová (jnz
).
Není-li podmínka splněna, tyto operace nemají žádný efekt a
výpočet pokračuje další instrukcí.
if cat == 0xf and ( op == 0 or # jmp
op == 1 and regs[ r_1 ] == 0 or # jz
op == 2 and regs[ r_1 ] != 0 ): # jnz
return imm, CONT
arith
definované níže.
if cat == 1:
regs[ r_out ] = arith( op, imm, regs[ r_1 ] )
if cat == 2:
regs[ r_out ] = arith( op, regs[ r_1 ], imm )
if cat == 3:
regs[ r_out ] = arith( op, regs[ r_1 ], regs[ r_2 ] )
if cat == 0xa:
regs[ r_out ] = compare( op, regs[ r_1 ], regs[ r_2 ] )
if cat == 0xb:
regs[ r_out ] = compare( op, regs[ r_1 ], imm )
return pc + 4, CONT
sp
(registr číslo
15).
def push( regs: list[ int ], mem: list[ int ], val: int ) -> None:
regs[ 15 ] = ( regs[ 15 ] - 2 ) % 65536
mem[ regs[ 15 ] ] = val
def pop( regs: list[ int ], mem: list[ int ] ) -> int:
rv = mem[ regs[ 15 ] ]
regs[ 15 ] = ( regs[ 15 ] + 2 ) % 65536
return rv
as_signed
bijektivně zobrazí celé číslo
z rozsahu
na číslo v rozsahu
, metodou
známou jako dvojkový doplňkový kód. Opačné zobrazení lze v Pythonu
provést velmi jednoduše, jako m % 2 ** b
. Protože stejný výraz
popisuje zkrácení výsledku, které se používá při bezznaménkové
aritmetice s pevnou šířkou slova, budeme jej níže zapisovat přímo,
bez použití pomocné funkce. Zobrazení realizované funkcí
as_signed
budeme níže značit
.
def as_signed( num: int, bits: int ) -> int:
mod = 2 ** bits
num = num % mod
return num if num < mod // 2 else num - mod
arith
realizuje základní aritmetické a logické
operace – sčítání, odečítání, násobení a dělení se zbytkem, bitové
logické operace a bitové posuvy. Vstupem jsou dvě celá čísla
op_1
a op_2
v rozsahu
, výsledek je v témže rozsahu.
def arith( op: int, op_1: int, op_2: int ) -> int:
sdiv
/udiv
a smod
/umod
.
if op == 0x1: return ( op_1 + op_2 ) % 65536
if op == 0x2: return ( op_1 - op_2 ) % 65536
if op == 0x3: return ( op_1 * op_2 ) % 65536
if op == 0x4: return op_1 // op_2
if op == 0x6: return op_1 % op_2
if op == 0x5 or op == 0x7: # signed div/rem
dividend = as_signed( op_1, 16 )
divisor = as_signed( op_2, 16 )
quot, rem = divmod( dividend, divisor )
if ( dividend > 0 ) != ( divisor > 0 ) and rem != 0:
rem -= divisor
if quot < 0 and rem != 0:
quot += 1
return ( quot if op == 0x5 else rem ) % 65536
Bitové logické operace se provádí po jednotlivých bitech
(číslicích ve dvojkovém zápisu). Každá operace provede na
odpovídajících bitech v operandech příslušnou logickou operaci
(and
, or
nebo xor
), čím získá odpovídající bit výsledku.
Operandy jsou vždy 16bitové.
if op in [ 0xa, 0xb, 0xc ]:
result = 0
for idx in range(16):
bit_1 = op_1 // 2 ** idx % 2
bit_2 = op_2 // 2 ** idx % 2
if op == 0xa:
bit_r = bit_1 == 1 and bit_2 == 1
if op == 0xb:
bit_r = bit_1 == 1 or bit_2 == 1
if op == 0xc:
bit_r = ( bit_1 == 1 ) != ( bit_2 == 1 )
result += bit_r * 2 ** idx
return result
Bitové posuvy jsou jednoduché – posuv doleva odpovídá násobení
a posuv doprava dělení příslušnou mocninou dvojky. Při pravých
posuvech (dělení) musíme opět rozlišit bezznaménkovou (shr
)
a znaménkovou (sar
) verzi. Znaménkový (tzv. aritmetický)
posuv pak lze chápat i jako operaci, která posouvá jednotlivé
bity doprava, ale zleva doplňuje místo nul kopie znaménkového
bitu.
if op == 0xd:
return ( op_1 * 2 ** op_2 ) % 65536
if op == 0xe:
return ( op_1 // 2 ** op_2 ) % 65536
if op == 0xf:
return ( as_signed( op_1, 16 ) // 2 ** op_2 ) % 65536
assert False
compare
realizuje aritmetická srovnání. Krom
rovnosti (
) jsou všechny operace závislé na tom, pracujeme-li
se znaménkovou reprezentací – v dvojkovém doplňkovém kódu jsou
kódy záporných čísel větší, než kódy čísel kladných, např.
16bitové kódování -1 je 0xffff
, přitom 16bitové kódování +1 je
0x0001
, výsledek 0xffff < 0x0001
ale jistě v tomto kontextu
nedává smysl.
def compare( op: int, arg_1: int, arg_2: int ) -> int:
sig_1 = as_signed( arg_1, 16 )
sig_2 = as_signed( arg_2, 16 )
if op == 0x0: result = arg_1 == arg_2
if op == 0xf: result = arg_1 != arg_2
if op == 0x1: result = arg_1 < arg_2
if op == 0x2: result = arg_1 <= arg_2
if op == 0x3: result = arg_1 > arg_2
if op == 0x4: result = arg_1 >= arg_2
if op == 0xa: result = sig_1 < sig_2
if op == 0xb: result = sig_1 <= sig_2
if op == 0xc: result = sig_1 > sig_2
if op == 0xd: result = sig_1 >= sig_2
return 1 if result else 0
step
a jejích pomocných funkcí hotova. Za
pomoci step
je již velmi jednoduché implementovat podprogram
run
, který provede celý výpočet. Program je uložen od adresy 0.
CONT = 0
HALT = 1
ERROR = 2
def run( regs: list[ int ], mem: list[ int ] ) -> bool:
print( " pc inst op_1 op_2 out │ " +
" rv l1 l2 l3 l4 l5 l6 l7 " +
" t1 t2 t3 t4 t5 t6 sp bp" )
status = CONT
pc = 0
while status == CONT:
pc, status = step( pc, regs, mem )
return status == HALT
read_program
, který ze vstupního
souboru přečte počáteční stav paměti (kterému můžeme také říkat
program).
def write_nibble( mem: list[ int ], index: int, nibble: int ) -> None:
mem[ index // 2 ] += nibble * 16 if index % 2 == 0 else nibble
def read_program( filename: str ) -> list[ int ]:
mem = [ 0 for i in range( 65536 ) ]
index = 0
with open( filename, 'r' ) as file:
for line in file.readlines():
if line[ 0 ] == ';':
break
for word in line.rstrip().replace( "'", '' ).split( ' ' ):
for hex_nibble in word:
write_nibble( mem, index, FROM_HEX[ hex_nibble ] )
index += 1
return mem
Zbývá vstupní bod (podprogram main
) a procedury pro výpis
aktuálního stavu výpočetního stroje. Zbývající kód není pro
pochopení funkčnosti výpočetního stroje tiny
nijak podstatný.
tiny
(v jazyce symbolických adres).
triangle
– trojúhelníková nerovnost,
factorial
– iterativní výpočet faktoriálu.
fib
–
-té Fibonacciho číslo (mod
),
adder
– dvouslovná sčítačka (32b),
gcd
– Euklidův algoritmus,
prime
– rozhodování prvočíselnosti,
popcnt
– počítání jedniček ve slově,
perfect
– součet dělitelů.
reverse
– otočení bitů ve slově,
hamming
– Hammingova vzdálenost slov,
packed
– sečtení dvou dvojic po složkách,
bitswap
– prohození dvou bitů ve slově,
collatz
– počítání kroků iterovaného výpočtu,
shift
– bitový posuv na části slova.
put
, která
nastaví výstupní registr na hodnotu přímého operandu. Zápis této
instrukce bude vypadat např. takto:
put 13 → rv
put 0x70 → l1
halt
rv
na hodnotu 13 a registr l1
na
hodnotu 112.
copy
– ta
nastaví výstupní registr na tutéž hodnotu, jakou má registr vstupní.
Například:
put 13 → rv
put 17 → l1
copy rv → l2 ; sets l2 = 13
copy l1 → rv ; sets rv = 17
halt
rv = 17
, l1 =
17
a l2 = 13
.
l1
odpovídá proměnné a
, registr l2
proměnné b
, registr rv
pak
proměnné x
.
název | python | tiny |
sčítání | x = a + b | add l1, l2 → rv |
odečítání | x = a - b | sub l1, l2 → rv |
násobení | x = a * b | mul l1, l2 → rv |
dělení | x = a // b | sdiv l1, l2 → rv |
udiv l1, l2 → rv | ||
zbytek | x = a % b | smod l1, l2 → rv |
umod l1, l2 → rv |
Podmínku z bodu (a) můžeme také chápat jako ⟦n ≥ 2ᵇ⁻¹⟧.
Pro 16bitová čísla, která budeme v tomto předmětu používat zdaleka
nejčastěji, to jsou tyto rozsahy:
0–ffff
v šestnáctkovém zápisu) pro
reprezentaci bez znaménka,
-8000
až 7fff
šestnáctkově) pro
reprezentaci se znaménkem.
add
funguje stejně dobře bez ohledu na to, chápeme-li
operandy jako znaménkové nebo bezznaménkové.
sdiv
, smod
) nebo nikoliv (udiv
,
umod
).
a ==
b
, a < b
, atp.
x
).
python | tiny | ||
x = a == b | eq l1, l2 → rv | 𝐞𝐪ual | |
x = a != b | ne l1, l2 → rv | 𝐧ot 𝐞qual | |
x = a < b | slt l1, l2 → rv | 𝐬igned | 𝐥ess 𝐭han |
ult l1, l2 → rv | 𝐮nsigned | 𝐥ess 𝐭han | |
x = a > b | sgt l1, l2 → rv | 𝐬igned | 𝐠reater 𝐭han |
ugt l1, l2 → rv | 𝐮nsigned | 𝐠reater 𝐭han | |
x = a <= b | sle l1, l2 → rv | 𝐬igned | 𝐥ess or 𝐞qual |
ule l1, l2 → rv | 𝐮nsigned | 𝐥ess or 𝐞qual | |
x = a >= b | sge l1, l2 → rv | 𝐬igned | 𝐠reater or 𝐞qual |
uge l1, l2 → rv | 𝐮nsigned | 𝐠reater or 𝐞qual |
rv
)
je u instrukcí z této rodiny vždy 1 (pravda) nebo 0 (nepravda). To
zejména znamená, že je možné tyto výsledky kombinovat operacemi
and
, or
a xor
a výsledek bude vždy opět 0 nebo 1, v souladu
s definicí příslušné logické operace (k těmto se vrátíme níže).
tiny
obsahuje 3 operace tohoto typu:
jmp addr
způsobí, že výpočet bude pokračovat od adresy addr
– bez ohledu na aktuální stav registrů; adresu můžeme (a typicky
budeme) zadávat jako symbol (jméno návěstí – viz též část
B.3),
jz reg, addr
(jump if zero) nejprve ověří, je-li hodnota
registru reg
nulová – pokud ano, provede skok stejně jako jmp
addr
, v případě opačném pokračuje na další instrukci bez
jakéhokoliv dalšího efektu,
jnz reg, addr
(jump if not zero) se chová stejně, ale skok
provede pouze je-li hodnota uložená v reg
nenulová.
put 1 → l1 ; a = 1
slt l1, 3 → t1 ; t = a < 3
jz t1, else ; if t:
then:
put 2 → l2 ; b = 2
jmp endif ; else:
else:
put 3 → l2 ; b = 3
endif:
halt
tinyvm.py
z kapitoly B, a také
upravit první instrukci na put 5 → l1
a srovnejte výsledek.
Podobně můžeme zapsat také while
cyklus (cykly for
do strojového
kódu přímo přepsat nemůžeme, ale jak jistě víte, je vždy možné
nejprve je přepsat na cykly while
). Uvažme tento velmi jednoduchý
program v Pythonu:
a = 1
while a < 3:
a += 1
while True
se realizuje snadno: pomocí
nepodmíněného skoku zpět (na nižší adresu).
put 1 → l1 ; a = 1
loop: ; while True:
slt l1, 3 → t1 ; t = a < 3
jz t1, end ; if not t: break
add l1, 1 → l1 ; a += 1
jmp loop
end:
halt
while podmínka
jsme přepsali na while True
a podmíněný
break
– ekvivalenci těchto dvou zápisů si rozmyslete.
triangle
](a + b > c) and (b + c > a) and (c + a > b)
l1
, l2
a l3
a výsledek (0
nebo 1) zapíšeme do registru rv
.
test: ; driver
ld l7, 0x24 → l1
add l7, 2 → l7
ld l7, 0x24 → l2
add l7, 2 → l7
ld l7, 0x24 → l3
add l7, 2 → l7
eq l1, 0xffff → t1 ; test-end marker
jz t1, solution
halt
data: ; l1 l2 l3 → rv
.word 3, 4, 5, 1
.word 1, 1, 1, 1
.word 1, 1, 3, 0
.word 2, 3, 1, 0
.word -1, 0
check:
ld l7, 0x24 → t1
eq rv, t1 → t1
asrt t1
add l7, 2 → l7
jmp test
.trigger set _tc_expect_ 4
.trigger inc _tc_
solution: ; zde začíná řešení
Protože potřebujeme implementovat konjunkci, nastavíme do registru
rv
její neutrální hodnotu – true
, tzn. 1. Každý ze tří testů
pak bude implementovaný stejně – sečte dvě strany, srovná tento
součet se stranou třetí a výsledek tohoto srovnání přidá do
registru rv
bitovou operací and
.
put 1 → rv
t1
(součet stran) a t2
(výsledek srovnání součtu se zbývající stranou).
add l1, l2 → t1
sgt t1, l3 → t2
and rv, t2 → rv
add l1, l3 → t1
sgt t1, l2 → t2
and rv, t2 → rv
add l2, l3 → t1
sgt t1, l1 → t2
and rv, t2 → rv
Tím je výpočet ukončen a protože je již výsledek uložen ve
správném registru, nezbývá než předat řízení zpátky do testovacího
kódu.
jmp check
factorial
]solution
. Vstupní hodnota bude v registru l6
, výsledek pak
v registru rv
.
test: ; driver
ld l7, 0x10 → l6
eq l6, 0xffff → t1 ; test-end marker
jz t1, solution
halt
data: ; input, expect
.word 1, 1
.word 2, 2
.word 3, 6
.word 4, 24
.word -1, 0
check:
add l7, 2 → l7
ld l7, 0x10 → t1
eq rv, t1 → t1
asrt t1
add l7, 2 → l7
jmp test
.trigger set _tc_expect_ 4
.trigger inc _tc_
solution: ; zde začíná řešení
Registr rv
budeme používat jako střadač (akumulátor) – před
vstupem do cyklu jej nastavíme na neutrální hodnotu 1 a v každé
iteraci jej vynásobíme počítadlem iterací.
l6
– protože na pořadí
násobení nezáleží, můžeme hodnoty násobit sestupně v pořadí
. Jakmile řídící proměnná dosáhne hodnoty nula,
cyklus ukončíme (musíme si pouze dát pozor, abychom nulou již
nenásobili).
put 1 → rv
loop:
mul rv, l6 → rv
sub l6, 1 → l6
jnz l6, loop
jmp check
fib
]l6
,
výsledek uložte do registru rv
. Po skončení výpočtu proveďte
skok na návěstí check
. Hodnotu registru l7
zachovejte.
adder
]l1
až l4
,
kde l1
a l3
jsou nižší a l2
a l4
jsou vyšší slova
sčítanců. Nižší slovo výsledku uložte do rv
, to vyšší pak do
l6
. Hodnotu v registru l7
neměňte.
gcd
]l1
a l2
– obě hodnoty interpretujte jako 16bitová celá čísla bez
znaménka.
rv
. Po ukončení výpočtu skočte na
návěstí check
. Hodnotu registru l7
neměňte.
prime
]l6
prvočíslem (hodnotu interpretujte jako číslo bez znaménka).
Výsledek (1 pokud prvočíslem je, 0 jinak) uložte do registru rv
a proveďte skok na návěstí check
. Hodnotu registru l7
nijak
neměňte.
popcnt
]l6
. Výsledek uložte do registru rv
a poté proveďte skok na návěstí check
. Hodnotu registru l7
nijak neměňte.
abundant
]l6
abundantním číslem (číslo
je abundantní, je-li součet
všech jeho dělitelů
). Vstup interpretujte jako číslo bez
znaménka.
rv
a proveďte skok na návěstí check
. Hodnotu registru l7
nijak
neměňte.
reverse
]l1
, výsledek uložte do rv
a skočte na návěstí
check
. Hodnotu v registru l7
neměňte.
hamming
]l1
a l2
.
Hammingovou vzdáleností je počet pozic, v nichž se vstupní slova
liší hodnotou bitu. Výsledek uložte do rv
a skočte na návěstí
check
. Hodnotu v registru l7
neměňte.
packed
]l1
a l2
, výsledek uložte do rv
a skočte na návěstí check
. Hodnotu v registru l7
neměňte.
bitswap
]l1
. Indexy bitů jsou
v l2
a l3
. Můžete předpokládat, že budou v rozsahu 0–15, nula
označuje nejméně významný bit slova. Výsledek uložte do rv
a skočte na návěstí check
. Hodnotu v registru l7
neměňte.
collatz
]f
na kladných celých číslech:
f(n) = n / 2 je-li n sudé
f(n) = 3n +1 je-li n liché
f
na vstup je poprvé
výsledkem jednička a
l1
. Počet aplikací funkce
uložte do rv
, nalezené maximum do l6
a skočte na návěstí
check
. Hodnotu v registru l7
neměňte.
shift
]rv
,
l2
a l3
(l2
je index
nejnižšího, l3
index nejvyššího bitu).
rv
), poté skočte na
návěstí check
. Hodnotu v registru l7
neměňte.
fib
– lokální proměnné, cyklus
prime
– aritmetika a rozsah číselných hodnot
gcd
– euklid podruhé
rand
– generování pseudonáhodných čísel
collatz
– počítání kroků iterovaného výpočtu
packed
– součet n-tic po složkách
popcnt
– počet nenulových cifer při daném základu
hamming
– hammingova vzdálenost při daném základu
palindrome
– binární palindrom
largest
– nejvyšší číslice při daném základu
factors
– rozklad na prvočinitele
primes
– rozklad na prvočinitele podruhé
transpose
– překlopení bitové matice
balanced
– vyvážená trojková soustava
digits
– ciferný součet při daném základu
rotate
– bitová rotace slova
int podprogram( int parametr₁, int parametr₂ )
{
…
}
int main()
{
assert( podprogram( 1, 2 ) == 3 );
…
}
main
bude v tomto kurzu vždy obsahovat testy,
které ověřují základní funkcionalitu ostatních podprogramů. Můžete
si do něj vždy přidat svoje vlastní testy. Zápis podprogram( 1,
2 )
je volání (použití) podprogramu – prozatím jej nebudeme mimo
testy potřebovat, protože jediné podprogramy, které budeme moct
v tomto předmětu použít, jsou ty, které si sami napíšeme.
typ jméno;
případně typ jméno = výraz;
. První forma proměnnou pouze
deklaruje, ale její počáteční hodnotu ponechá neurčenu – tuto
hodnotu není dovoleno použít.
unsigned
– celé číslo v rozsahu 0 až 65535,¹
int
– celé číslo v rozsahu -32768 do 32767,9
bool
– celé číslo, 0 nebo 1, které typicky reprezentuje
pravdivostní hodnotu – 0 pro false
, 1 pro true
,
signed char
– celé číslo v rozsahu -128 až 127,
unsigned char
– celé číslo v rozsahu 0 až 255,
char
– typ se stejným rozsahem jako jeden z předchozích dvou
(který z nich je určeno implementací), ale přesto z pohledu
kontroly typů od obou odlišný.
{
// zde x ještě není deklarováno
int x;
{
int y;
… // zde můžeme použít jak x tak y
} // zde končí rozsah platnosti y
… // zde již y není lze použít
} // zde končí rozsah platnosti x
e₁
, e₂
… eₙ
výrazy,
var
jméno proměnné,
lit
číselný literál (konstanta),
lit
(konstanta) je výraz,
var
(jméno proměnné) je výraz,
e₁ + e₂
, e₁ - e₂
,
e₁ * e₂
, e₁ / e₂
, e₁ % e₂
(modulo)
-e₁
,
e₁ == e₂
(rovnost), e₁ != e₂
(nerovnost)
e₁ <= e₂
, e₁ >= e₂
, e₁ < e₂
, e₁ > e₂
e₁ & e₂
(and), e₁ | e₂
(or), e₁ ^ e₂
(xor),
~e₁
– bitová negace,
e₁ >> e₂
, e₁ << e₂
,
var = e₁
,
var += e₁
, var -= e₁
,
var *= e₁
, var /= e₁
, var %= e₁
,
var <<= e₁
, var >>= e₂
,
var &= e₁
, var ^= e₁
, var |= e₁
,
++var
, --var
,
var++
, var--
,
e₁, e₂
,
e₁ && e₂
(and), e₁ || e₂
(or),
!e₁
,
e₁ ? e₂ : e₃
,
tiny
. Abychom mohli výpočet skutečně provést, musíme určit
registr, do kterého má být výsledek zapsán – budeme mluvit
o vyhodnocení výrazu E do registru R.14
lit
se vyhodnotí přímo na číselnou hodnotu zapsanou ve
zdrojovém kódu. Například vyhodnocení výrazu 7
do registru
rv
se realizuje instrukcí put 7 → rv
.
var
se vyhodnotí na hodnotu, která je v momentě
vyhodnocení tohoto výrazu uložena v objektu svázaném s proměnnou
var
. Prozatím uvažujeme pouze situace, kdy je objekt svázaný
s var
uložen přímo v registru. Je-li např. var
uloženo
v l1
, vyhodnocení výrazu var
do registru rv
realizujeme
instrukcí copy l1 → rv
.
e₁ + e₂
. Víme, že e₁
a e₂
popisují
nějaké hodnoty. Abychom mohli vyčíslit hodnotu e₁ + e₂
, budeme
nejprve potřebovat tyto hodnoty. Na to použijeme dočasné
registry – vyhodnocení e₁ + e₂
do rv
bude vypadat takto:
e₁
do registru t₁
,
e₂
do registru t₂
,
add t₁, t₂ → rv
.
e₂
nepřepíše registr t₁
– jak přesně se toho dosáhne budeme zkoumat později.15
Analogicky se vypočtou ostatní aritmetické, bitové, atd.
operátory (k hodnotám s/bez znaménka a operacím dělení se ještě
vrátíme).
var = e₁
mají krom hodnoty také vedlejší efekt
– zápis do objektu svázaného s proměnnou var
. Jejich realizace
vypadá takto – vyhodnocujeme do registru rv
, objekt svázaný
s var
nechť žije v l1
:
e₁
do registru rv
copy rv → l1
.
e₁
je zároveň hodnotou celého výrazu,
a zůstává uložená v registru rv
, jak bylo požadováno.
var += e₁
je analogické, pouze je operaci
copy
předřazena příslušná aritmetická nebo logická operace:16
e₁
do registru t₁
add t1, l1 → rv
,
copy rv → l1
.
++var
, --var
jsou pouze syntaktické zkratky pro var += 1
resp. var -= 1
,
ale postfixové se liší – vyhodnocení var++
do rv
proběhne
takto (var
je svázáno s l1
):
copy l1 → rv
,
add l1, 1 → l1
.
var++
je tedy původní hodnota var
,
předtím, než bylo provedeno zvýšení proměnné o jedničku.
e₁, e₂
představuje „zapomenutí hodnoty“ výrazu e₁
–
výraz e₁
je proveden pouze pro svoje vedlejší efekty (např.
výše uvedené přiřazení). Vyhodnocení e₁, e₂
do registru rv
lze realizovat např. takto:
e₁
do rv
,
e₂
do rv
.
var + 1
se vypočte XXX
tinycc
).
var += e₁
ekvivalentní výrazu var = var + e₁
. V obecném případě, kdy je na levé straně složeného přiřazení složitější výraz (a nikoliv pouze název proměnné), to už ale neplatí!
if
, else
for
while
break
continue
fib
]int fib( int n )
{
a
, b
. V jazyce C jsou proměnné pevně svázány
s objekty – jakmile proměnná (jméno) vznikne, její vazbu na
objekt již není možné změnit. Operátor přiřazení způsobí
změnu hodnoty objektu. Více o proměnných a přiřazení
naleznete v sekci 2.2.
int
,
int a = 1, b = 1;
Jazyk C má 3 typy cyklů. Cyklus for
používáme zejména
v situaci, kdy předem známe potřebný počet iterací. Má
následující strukturu:
for
,
true
),
i < n - 2
je složený
výraz – jedná se o použití operátoru <
(menší než), přitom
pravý operand – n - 2
je opět použití operátoru (-
,
odečítání). Je velmi důležité si uvědomit, že náš výpočetní
stroj takové výrazy neumí přímo zpracovat – dekomponovat
takovéto výrazy na sekvence instrukcí je jednou z hlavních
úloh překladače jazyka C.
$ tinycc -S d1_fib.c
.cond.1
– jedná se o instrukce sub l1, 2 → t1
a
slt l4, t1 → t1
. Překladač zde použil registr t1
pro
uložení mezivýsledku, který je na úrovni jazyka C
implicitní a nemá žádné jméno.
for ( int i = 0; i < n - 2; ++i )
{
i
).
int c = a + b;
a = b;
b = c;
}
return
ukončí vykonáváni podprogramu a volajícímu předá návratovou
hodnotu, podobně jako v jazyce Python.
return b;
}
int main() /* demo */
{
Na tomto místě opět trochu předběhneme látku – použití
(volání) podprogramu je výraz a jeho vyhodnocení odpovídá
naší intuitivní představě: skutečné parametry (uvedené
v kulatých závorkách za jménem) se použijí jako pomyslné
inicializátory formálních parametrů a s takto
inicializovanými parametry se vykoná tělo podprogramu. Po
jeho ukončení se výraz volání podprogramu vyhodnotí na
návratovou hodnotu. Jak se tento mechanismus realizuje na
úrovni výpočetního stroje si povíme v příští kapitole.
assert
(tvrzení) vyhodnotí svůj parametr a
je-li výsledek false
, program ukončí s chybou.
assert( fib( 1 ) == 1 );
assert( fib( 2 ) == 1 );
assert( fib( 7 ) == 13 );
assert( fib( 20 ) == 6765 );
return 0;
}
prime
]number
jako hodnotu typu unsigned
– celé číslo bez znaménka.
Protože náš cílový stroj – tiny
– má slova velikosti 16 bitů,
bude i typ unsigned
16bitový.17
bool is_prime( unsigned number )
{
if ( number < 2 )
return false;
divisor
budeme udržovat aktuální pokusný
dělitel.
unsigned divisor = 2;
divisor < number
. To je ale relativně neefektivní, protože
je-li
prvočíslo, provedeme mnoho zbytečných iterací.
tiny
, tak
v jazyce C). Přesto ale narazíme na problém: nepracujeme
totiž se skutečnými celými čísly, ale s 16bitovými slovy.
0x10a19
– pokusíme-li se toto číslo zapsat do
16 bitů, dostaneme 0x0a19
, desítkově 2585.
divisor = 255
se
divisor * divisor
vyhodnotí na 65025, což je méně než 65521
a proto se vykoná tělo cyklu. V další iteraci dostaneme
divisor = 256
, ale divisor * divisor
se na 16 bitech
vyhodnotí na nulu a do cyklu tedy opět vstoupíme – a to
i přesto, že při standardním celočíselném násobení bychom
dostali výsledek
. Tento problém bude
pokračovat po mnoho iterací – takto zapsaný cyklus se zastaví
až pro hodnotu divisor = 16203
, kdy dostáváme:
0xfa5fff9
,
0xfff9
, desítkově tedy 65529.
unsigned sqrt_uint_max = 1 << sizeof( unsigned ) * 4;
while ( divisor <= sqrt_uint_max &&
divisor * divisor <= number )
{
Zjistíme-li, že divisor
dělí hodnotu number
beze
zbytku, jistě to znamená, že number
není prvočíslo
(divisor
nemůže být ani 1 ani rovný number
).
if ( number % divisor == 0 )
return false;
++ divisor;
}
return true;
}
int main() /* demo */
{
assert( is_prime( 2 ) );
assert( is_prime( 3 ) );
assert( is_prime( 5 ) );
assert( is_prime( 13 ) );
assert( is_prime( 29 ) );
assert( is_prime( 97 ) );
assert( is_prime( 619 ) );
assert( !is_prime( 1 ) );
assert( !is_prime( 4 ) );
assert( !is_prime( 6 ) );
assert( !is_prime( 8 ) );
assert( !is_prime( 68 ) );
assert( !is_prime( 77 ) );
assert( !is_prime( 81 ) );
assert( !is_prime( 323 ) );
assert( !is_prime( 36863u ) );
assert( is_prime( 65521u ) );
}
unsigned
32bitový (a to i na 64bitových architekturách), ale není to nijak zaručeno.
gcd
]gcd
, který Euklidovým algoritmem
spočte největší společný dělitel dvou přirozených čísel, která
obdrží jako argumenty.
unsigned gcd( unsigned x, unsigned y )
{
x
a y
jsou proměnné obsahující vstupní
čísla. Výsledek vraťte příkazem return
.
return x;
}
rand
]hi
(horních 16 bitů) a lo
(spodních 16 bitů). Protože zatím
neumíme z podprogramu vrátit dvě hodnoty zároveň, napíšete
podprogramy dva, každý vracející jednu polovinu 32bitového čísla.
rand( prev ) = prev · 1103515245 + 12345
rand_hi vrací horních 16 bitů výsledku.
unsigned rand_hi( unsigned hi, unsigned lo )
{
return 0;
}
rand_lo
vrací spodních 16 bitů výsledku.
unsigned rand_lo( unsigned hi, unsigned lo )
{
return 0;
}
2.p.3 [collatz
]
Uvažme následující funkci18 f
na kladných celých číslech:
f(n) = n / 2 je-li n sudé
f(n) = 3n +1 je-li n liché
Collatzova domněnka říká, že budeme-li na libovolné kladné celé
číslo tuto funkci opakovaně aplikovat, dostaneme se nakonec
k výsledku 1.
Implementujte podprogram collatz_lds
, který na svůj vstup bude
opakovaně aplikovat funkci f
než dojde k jedničce. Výsledkem
podprogramu bude délka nejdelší klesající posloupnosti
mezivýsledků, tj. kolikrát nejvíce po sobě prováděl půlení čísla.
Můžete předpokládat, že pro číslo na vstupu domněnka skutečně platí
a že v průběhu výpočtu nevznikne mezivýsledek, který by se nevlezl
do šestnáctibitového registru.
unsigned collatz_lds( unsigned n )
{
return 0;
}
18 Ač mluvíme o matematické funkci f
, neočekáváme, že tato bude implementována samostatným podprogramem (tzv. funkcí) jazyka C. O těch zatím v kurzu řeč nebyla.
2.p.4 [packed
]
Uvažme slovo, které nereprezentuje jedno šestnáctibitové číslo,
ale
-tici několika kratších čísel uložených vedle sebe
(například 2 osmibitová nebo 4 čtyřbitová). Vaším úkolem bude
čísla ve dvou takovýchto
-ticích sečíst po složkách a výsledek
opět vrátit jako
-tici stejného tvaru.
Implementujte podprogram packed_add
, který toto sčítání provede.
Jeho první dva argumenty jsou slova reprezentující
-tice,
třetím je šířka čísla v
-tici. V případě, že tato šířka nedělí
šířku slova, je číslo uložené v nejvyšších bitech kratší. Můžete
předpokládat, že šířka nebude nulová.
unsigned packed_add( unsigned v1, unsigned v2, unsigned width )
{
Řešení pište sem. v1
a v2
jsou proměnné obsahující vstupní
n-tice, width
obsahuje šířku čísla v n-tici. Výsledek vraťte
příkazem return
return 0;
}
2.p.5 [popcnt
]
Implementujte podprogram popcnt
, který spočítá počet nenulových
číslic representace zadaného čísla při zadaném základu. Můžete
předpokládat, že základ je alespoň dva.
unsigned popcnt( unsigned n, unsigned base )
{
Řešení pište sem. n
je proměnná obsahující vstupní číslo,
base
obsahuje základ. Výsledek vraťte příkazem return
return 0;
}
2.p.6 [hamming
]
Implementujte podprogram hamming
, který spočítá Hammmingovu
vzdálenost mezi reprezentacemi dvou zadaných čísel při daném
číselném základu, tj. počet pozic, v nichž se reprezentace čísel
liší cifrou. Je-li jedna z reprezentací kratší, doplňte ji zleva
nulami do délky delší reprezentace. Můžete předpokládat, že
základ je alespoň dva.
unsigned hamming( unsigned x, unsigned y, unsigned base )
{
Řešení pište sem. x
a y
jsou proměnné obsahující vstupní
čísla, base
obsahuje základ. Výsledek vraťte příkazem
return
return 0;
}
2.r Řešené úlohy
2.r.1 [palindrome
]
Implementujte predikát is_binary_palindrome
, který o zadaném
čísle rozhodne, zda je jeho binární reprezentace palindromem.
bool is_binary_palindrome( unsigned n )
{
Řešení pište sem.
return false;
}
2.r.2 [largest
]
Implementujte podprogram largest_digit
, který pro zadané číslo
n
vrátí jeho nejvyšší číslici při základu base
.
unsigned largest_digit( unsigned n, unsigned base )
{
Řešení pište sem.
return 0;
}
2.r.3 [factors
]
Implementujte podprogram factor_count
, který vrátí počet všech
činitelů v prvočíselném rozkladu zadaného kladného čísla. Opakující
se prvočinitele započítejte opakovaně.
unsigned factor_count( unsigned n )
{
assert( n > 0 );
Řešení pište sem.
return 0;
}
2.r.4 [primes
]
Implementujte podprogram prime_count
, který vrátí počet
unikátních činitelů v prvočíselném rozkladu zadaného kladného
čísla. Opakující se prvočinitele započítejte pouze jednou.
unsigned prime_count( unsigned n )
{
assert( n > 0 );
Řešení pište sem.
return 0;
}
2.r.5 [transpose
]
Označíme-li postupně bity ve slově tak, že nejméně významný je 0
a nejvýznamnější je F
, pak slovo reprezentuje následující bitové
čtvercové matice o velikostech 4×4 až 1×1:
F E D C 8 7 6 3 2 0
B A 9 8 5 4 3 1 0
7 6 5 4 2 1 0
3 2 1 0
Implementujte podprogram transpose
, který zadanou matici
transponuje, tj. vrátí slovo reprezentující jednu z matic:
F B 7 3 8 5 2 3 1 0
E A 6 2 7 4 1 2 0
D 9 5 1 6 3 0
C 8 4 0
Velikost jedné strany matice je také argumentem podprogramu. Můžete
předpokládat, že se bude jednat o číslo mezi 1 a 4. Pro velikosti
menší než 4 nevyužité bity na vstupu ignorujte a ve výsledku nechť
jsou nulové.
unsigned transpose( unsigned m, int size )
{
Řešení pište sem.
return 0;
}
2.r.6 [balanced
]
Vyvážená trojková soustava nepoužívá číslice s hodnotami 0, 1 a 2,
nýbrž 0, 1 a -1 (zapisované zde 0, +
a -
). Například číslo dvě
se v ní representuje jako +-
:
.
Implementujte podprogram balanced_digits
, který vrátí dvě 8bitová
čísla zabalená (jako v p4_packed
) do jednoho slova: spodní
slabika bude obsahovat počet číslic +
a horní počet číslic -
ve vyváženětrojkovém zápisu zadaného celého čísla.
unsigned balanced_digits( int n )
{
Řešení pište sem.
return 0;
}
2.v Volitelné úlohy
2.v.1 [digits
]
Implementujte podprogram digit_sum
, který spočítá ciferný součet
čísla při zadaném základu. Můžete předpokládat, že základ je
alespoň dva.
unsigned digit_sum( unsigned n, unsigned base )
{
Řešení pište sem.
return 0;
}
2.v.2 [rotate
]
Implementujte podprogram rotate
, který provede bitovou rotaci
zadaného slova o zadaný počet pozic. Kladný počet provádí rotaci
vlevo, záporný vpravo.
unsigned rotate( unsigned word, int amount )
{
Řešení pište sem.
return 0;
}
K Vzorová řešení
K.1 Týden 1
K.1.01.1 [reverse
]
put 0x0001 → t1 ; maska zdrojového bitu
put 0x8000 → t2 ; maska cílového bitu
put 0 → rv
loop:
and l1, t1 → t3 ; zjištění zdrojového bitu
jz t3, zero
or rv, t2 → rv ; nastavení cílového bitu
zero:
shl t1, 1 → t1 ; posuv obou masek
shr t2, 1 → t2
jz t2, check ; skončit, když cílový bit vypadl z registru
jmp loop
\blank
K.1.01.2 [hamming
]
put 0 → rv
put 0x8000 → t3 ; maska srovnávaného bitu
loop:
and t3, l1 → t1 ; hodnoty srovnávaných bitů
and t3, l2 → t2
ne t1, t2 → t4 ; zvýšit čítač, pokud se bity nerovnají
add rv, t4 → rv
shr t3, 1 → t3 ; posunout masku srovnávaného bitu
jz t3, check ; konec, pokud maskovaný bit vypadl z registru
jmp loop
\blank
K.1.01.3 [packed
]
add l1, l2 → rv ; součet spodní slabiky
and rv, 0x00ff → rv
and l1, 0xff00 → l1 ; vynulovat spodní slabiky vstupů
and l2, 0xff00 → l2
add l1, l2 → t1 ; součet horních slabik
or t1, rv → rv ; promítnout horní součet do výsledku
jmp check
\blank
K.1.01.4 [bitswap
]
put 0x0000 → rv
shl 1, l2 → t2 ; masky zadaných bitů
shl 1, l3 → t3
and l1, t2 → t4 ; extrakce zadaných bitů
and l1, t3 → t5
xor t2, -1 → t6 ; vynulování zadaných bitů
and l1, t6 → rv
xor t3, -1 → t6
and rv, t6 → rv
jz t4, zero ; nastavení bitů
or rv, t3 → rv
zero:
jz t5, check
or rv, t2 → rv
jmp check
\blank
K.1.01.5 [collatz
]
put 0 → rv ; vynulovat výstupní registry
put 0 → l6
check_max:
ugt l1, l6 → t1 ; ověřit a nastavit nové maximum
jz t1, loop
copy l1 → l6
loop:
eq l1, 1 → t1 ; ukončit při jedničce
jnz t1, check
add rv, 1 → rv ; jinak zvýšit čítač
and l1, 1 → t1 ; test sudosti
jz t1, even
mul l1, 3 → l1 ; n = 3n + 1
add l1, 1 → l1
jmp check_max ; možná jsme našli nové maximum
even:
udiv l1, 2 → l1 ; n = n / 2
jmp loop ; při dělení jsme jistě maximum nenašli
\blank
K.1.01.6 [shift
]
put 0xffff → t2 ; do t2 nachystáme masku
sub 15, l3 → t3 ; počet horních nulových bitů
shl t2, t3 → t2 ; vynulujeme horní bity
shr t2, t3 → t2
shr t2, l2 → t2 ; vynulujeme spodní bity
shl t2, l2 → t2
xor t2, -1 → t3 ; inverzní maska
and rv, t2 → t4
shl t4, 1 → t4
and t4, t2 → t4
and rv, t3 → rv
or rv, t4 → rv
jmp check
T Technické informace
Tato kapitola obsahuje informace o technické realizaci předmětu,
a to zejména:
- jak se pracuje s kostrami úloh,
- jak sdílet obrazovku (terminál) ve cvičení,
- jak se odevzdávají úkoly,
- kde najdete výsledky testů a jak je přečtete,
- kde najdete hodnocení kvality kódu (učitelské recenze),
- jak získáte kód pro vzájemné recenze.
T.1 Informační systém
Informační systém tvoří primární „rozhraní“ pro stahování studijních
materiálů, odevzdávání řešení, získání výsledků vyhodnocení a čtení
recenzí. Zároveň slouží jako hlavní komunikační kanál mezi studenty
a učiteli, prostřednictvím diskusního fóra.
T.1.1 Diskusní fórum
Máte-li dotazy k úlohám, organizaci, atp., využijte k jejich
položení prosím vždy přednostně diskusní fórum.19 Ke každé kapitole a
ke každému příkladu ze sady vytvoříme samostatné vlákno, kam patří
dotazy specifické pro tuto kapitolu nebo tento příklad. Pro řešení
obecných organizačních záležitostí a technických problémů jsou
podobně v diskusním fóru nachystaná vlákna.
Než položíte libovolný dotaz, přečtěte si relevantní část dosavadní
diskuse – je možné, že na stejný problém už někdo narazil. Máte-li
ve fóru dotaz, na který se Vám nedostalo do druhého pracovního dne
reakce, připomeňte se prosím tím, že na tento svůj příspěvek
odpovíte.
Máte-li dotaz k výsledku testu, nikdy tento výsledek nevkládejte do
příspěvku (podobně nikdy nevkládejte části řešení příkladu). Učitelé
mají přístup k obsahu Vašich poznámkových bloků, i k Vámi odevzdaným
souborům. Je-li to pro pochopení kontextu ostatními čtenáři potřeba,
odpovídající učitel chybějící informace doplní dle uvážení.
19 Nebojte se do fóra napsat – když si s něčím nevíte rady a/nebo nemůžete najít v materiálech, rádi Vám pomůžeme nebo Vás nasměrujeme na místo, kde odpověď naleznete.
T.1.2 Stažení koster
Kostry naleznete ve studijních materiálech v ISu: Student
→
PB111
→ Studijní materály
→ Učební materiály
. Každá kapitola
má vlastní složku, pojmenovanou 00
(tento úvod a materiály
k nultému cvičení), 01
(první běžná kapitola), 02
, …, 12
.
Veškeré soubory stáhnete jednoduše tak, že na složku kliknete pravým
tlačítkem a vyberete možnost Stáhnout jako ZIP
. Stažený soubor
rozbalte a můžete řešit.
T.1.3 Odevzdání řešení
Vypracované příklady můžete odevzdat do odevzdávárny v ISu:
Student
→ PB111
→ Odevzdávárny
. Pro přípravy používejte
odpovídající složky s názvy 01
, …, 12
. Pro příklady ze sad pak
s1_a_csv
, atp. (složky začínající s1
pro první, s2
pro druhou
a s3
pro třetí sadu).
Soubor vložíte výběrem možnosti Soubor – nahrát
(první ikonka na
liště nad seznamem souborů). Tímto způsobem můžete najednou nahrát
souborů několik (například všechny přípravy z dané kapitoly). Vždy
se ujistěte, že vkládáte správnou verzi souboru (a že nemáte
v textovém editoru neuložené změny). Pozor! Všechny vložené
soubory se musí jmenovat stejně jako v kostrách, jinak nebudou
rozeznány (IS při vkládání automaticky předřadí Vaše UČO – to je
v pořádku, název souboru po vložení do ISu neměňte) .
O každém odevzdaném souboru (i nerozeznaném) se Vám v poznámkovém
bloku log
objeví záznam. Tento záznam i výsledky testu syntaxe by
se měl objevit do několika minut od odevzdání (nemáte-li ani po 15
minutách výsledky, napište prosím do diskusního fóra).
Archiv všech souborů, které jste úspěšně odevzdali, naleznete ve
složce Private
ve studijních materiálech (Student
→ PB111
→
Studijní materiály
→ Private
).
T.1.4 Výsledky automatických testů
Automatickou zpětnou vazbu k odevzdaným úlohám budete dostávat
prostřednictvím tzv. poznámkových bloků v ISu. Ke každé
odevzdávárně existuje odpovídající poznámkový blok, ve kterém
naleznete aktuální výsledky testů. Pro přípravy bude blok vypadat
přibližně takto:
testing verity of submission from 2025-09-17 22:43 CEST
subtest p1_foo passed [ 1]
subtest p2_bar failed
subtest p3_baz failed
subtest p4_quux passed [ 1]
subtest p5_wibble passed [ 1]
subtest p6_xyzzy failed
{bližší popis chyby}
verity test failed
testing syntax of submission from 2025-09-17 22:43 CEST
subtest p1_foo passed
subtest p2_bar failed
{bližší popis chyby}
subtest p3_baz failed
{bližší popis chyby}
subtest p4_quux passed
subtest p5_wibble passed
subtest p6_xyzzy passed
syntax test failed
testing sanity of submission from 2025-09-17 22:43 CEST
subtest p1_foo passed [ 1]
subtest p2_bar failed
subtest p3_baz failed
subtest p4_quux passed [ 1]
subtest p5_wibble passed [ 1]
subtest p6_xyzzy passed [ 1]
sanity test failed
best submission: 2025-09-17 22:43 CEST worth *7 point(s)
Jednak si všimněte, že každý odstavec má vlastní časové razítko,
které určuje, ke kterému odevzdání daný výstup patří. Tato časová
razítka nemusí být stejná. V hranatých závorkách jsou uvedeny dílčí
body, za hvězdičkou na posledním řádku pak celkový bodový zisk za
tuto kapitolu.
Také si všimněte, že best submission
se vztahuje na jedno
konkrétní odevzdání jako celek: v situaci, kdy odstavec „verity“ a
odstavec „sanity“ nemají stejné časové razítko, nemusí být celkový
bodový zisk součtem všech dílčích bodů. O konečném zisku rozhoduje
vždy poslední odevzdání před příslušným termínem (opět jako jeden
celek).20
Výstup pro příklady ze sad je podobný, uvažme například:
testing verity of submission from 2025-10-11 21:14 CEST
subtest foo-small passed
subtest foo-large passed
verity test passed [ 7]
testing syntax of submission from 2025-10-14 23:54 CEST
subtest build passed
syntax test passed
testing sanity of submission from 2025-10-14 23:54 CEST
subtest foo passed
sanity test passed
best submission: 2025-10-11 21:14 CEST worth *7 point(s)
Opět si všimněte, že časová razítka se mohou lišit (a v případě
příkladů ze sady bude k této situaci docházet poměrně často, vždy
tedy nejprve ověřte, ke kterému odevzdání se který odstavec vztahuje
a pak až jej dále interpretujte).
20 Můžete si tak odevzdáním nefunkčních řešení na poslední chvíli snížit výsledný bodový zisk. Uvažte situaci, kdy máte v pátek 4 body za sanity příkladů p1, p2, p3, p6 a 1 bod za verity p1, p2. V sobotu odevzdáte řešení, kde p1 neprochází sanity testem, ale p4 ano a navíc projdou verity testy příklady p4 a p6. Váš výsledný zisk bude 5.5 bodu. Tento mechanismus Vám nikdy nesníží výsledný bodový zisk pod již jednou dosaženou hranici „best submission“.
T.1.5 Další poznámkové bloky
Blok corr
obsahuje záznamy o manuálních bodových korekcích (např.
v situaci, kdy byl Váš bodový zisk ovlivněn chybou v testech).
Podobně se zde objeví záznamy o penalizaci za opisování.
Blok log
obsahuje záznam o všech odevzdaných souborech, včetně
těch, které nebyly rozeznány. Nedostanete-li po odevzdání příkladu
výsledek testů, ověřte si v tomto poznámkovém bloku, že soubor byl
správně rozeznán.
Blok misc
obsahuje záznamy o Vaší aktivitě ve cvičení (netýká se
bodů za vzájemné recenze ani vnitrosemestrální testy). Nemáte-li
před koncem cvičení, ve kterém jste řešili příklad u tabule, záznam
v tomto bloku, připomeňte se svému cvičícímu.
Konečně blok sum
obsahuje souhrn bodů, které jste dosud získali, a
které ještě získat můžete. Dostanete-li se do situace, kdy Vám ani
zisk všech zbývajících bodů nebude stačit pro splnění podmínek
předmětu, tento blok Vás o tom bude informovat. Tento blok má navíc
přístupnou statistiku bodů – můžete tak srovnat svůj dosavadní
bodový zisk se svými spolužáky.
Je-li blok sum
v rozporu s pravidly uvedenými v tomto dokumentu,
přednost mají pravidla zde uvedená. Podobně mají v případě
nesrovnalosti přednost dílčí poznámkové bloky. Dojde-li k takovéto
neshodě, informujte nás o tom prosím v diskusním fóru. Případná
známka uvedená v poznámkovém bloku sum
je podobně pouze
informativní – rozhoduje vždy známka zapsaná v hodnocení předmětu.
T.2 Studentský server aisa
Použití serveru aisa
pro odevzdávání příkladů je zcela volitelné a
vše potřebné můžete vždy udělat i prostřednictvím ISu. Nevíte-li si
s něčím z níže uvedeného rady, použijte IS.
Na server aisa
se přihlásíte programem ssh
, který je k dispozici
v prakticky každém moderním operačním systému (v OS Windows skrze
WSL21 – Windows Subsystem for Linux). Konkrétní příkaz (za xlogin
doplňte ten svůj):
$ ssh xlogin@aisa.fi.muni.cz
Program se zeptá na heslo: použijte to fakultní (to stejné, které
používáte k přihlášení na ostatní fakultní počítače, nebo např. ve
fadmin
-u nebo fakultním gitlab
-u).
21 Jako alternativu, nechcete-li z nějakého důvodu WSL instalovat, lze použít program putty
.
T.2.1 Pracovní stanice
Veškeré instrukce, které zde uvádíme pro použití na stroji aisa
platí beze změn také na libovolné školní UNIX-ové pracovní stanici
(tzn. z fakultních počítačů není potřeba se hlásit na stroj aisa
,
navíc mají sdílený domovský adresář, takže svoje soubory z tohoto
serveru přímo vidíte, jako by byly uloženy na pracovní stanici).
T.2.2 Stažení koster
Aktuální zdrojový balík stáhnete příkazem:
$ pb111 update
Stažené soubory pak naleznete ve složce ~/pb111
. Je bezpečné tento
příkaz použít i v případě, že ve své kopii již máte rozpracovaná
řešení – systém je při aktualizaci nepřepisuje. Došlo-li ke změně
kostry u příkladu, který máte lokálně modifikovaný, aktualizovanou
kostru naleznete v souboru s dodatečnou příponou .pristine
, např.
01/e2_concat.cpp.pristine
. V takovém případě si můžete obě verze
srovnat příkazem diff
:
$ diff -u e2_concat.cpp e2_concat.cpp.pristine
Případné relevantní změny si pak již lehce přenesete do svého
řešení.
Krom samotného zdrojového balíku Vám příkaz pb111 update
stáhne i
veškeré recenze (jak od učitelů, tak od spolužáků). To, že máte
k dispozici nové recenze, uvidíte ve výpisu. Recenze najdete ve
složce ~/pb111/reviews
.
T.2.3 Odevzdání řešení
Odevzdat vypracované (nebo i rozpracované) řešení můžete ze složky
s relevantními soubory takto:
$ cd ~/pb111/01
$ pb111 submit
Přidáte-li přepínač --wait
, příkaz vyčká na vyhodnocení testů fáze
„syntax“ a jakmile je výsledek k dispozici, vypíše obsah příslušného
poznámkového bloku. Chcete-li si ověřit co a kdy jste odevzdali,
můžete použít příkaz
$ pb111 status
nebo se podívat do informačního systému (blíže popsáno v sekci T.1).
Pozor! Odevzdáváte-li stejnou sadu příprav jak v ISu tak
prostřednictvím příkazu pb111
, ujistěte se, že odevzdáváte vždy
všechny příklady.
T.2.4 Sdílení terminálu
Řešíte-li příklad typu r
ve cvičení, bude se Vám pravděpodobně
hodit režim sdílení terminálu s cvičícím (který tak bude moct
promítat Váš zdrojový kód na plátno, případně do něj jednoduše
zasáhnout).
Protože se sdílí pouze terminál, budete se muset spokojit
s negrafickým textovým editorem (doporučujeme použít micro
,
případně vim
umíte-li ho ovládat). Spojení navážete příkazem:
$ pb111 beamer
Protože příkaz vytvoří nové sezení, nezapomeňte se přesunout do
správné složky příkazem cd ~/pb111/NN
.
T.3 Kostry úloh
Pracujete-li na studentském serveru aisa
, můžete pro překlad
jednotlivých příkladů použít přiložený soubor makefile
, a to
zadáním příkazu
$ make příklad.bin
kde příklad
je název souboru bez přípony (např. tedy make
p1_fib.bin
). Tento příkaz přeloží program překladačem tinycc
a
rovnou jej také spustí, tzn. selže-li nějaký test, tuto informaci
rovnou uvidíte na obrazovce.
Selže-li některý krok, další už se provádět nebude. Povede-li se
překlad v prvním kroku, v pracovním adresáři naleznete spustitelný
soubor s názvem příklad.bin
, se kterým můžete dále pracovat (např.
jej načíst do virtuálního stroje tinyvm.py
z kapitoly B).
Existující přeložené soubory můžete smazat příkazem make clean
(vynutíte tak jejich opětovný překlad a spuštění všech kontrol).
T.3.1 Textový editor
Na stroji aisa
je k dispozici jednoduchý editor micro
, který má
podobné ovládání jako klasické textové editory, které pracují
v grafickém režimu, a který má slušnou podporu pro práci se
zdrojovým kódem. Doporučujeme zejména méně pokročilým. Další
možností jsou samozřejmě pokročilé editory vim
a emacs
.
Mimo lokálně dostupné editory si můžete ve svém oblíbeném editoru,
který máte nainstalovaný u sebe, nastavit režim vzdálené editace
(použitím protokolu ssh
). Minimálně ve VS Code je takový režim
k dispozici a je uspokojivě funkční.
T.3.2 Vlastní prostředí
Prozatím je překladač tinycc
k dispozici pouze na stroji aisa
.
Jakmile to bude možné, zveřejníme zdrojové kódy a/nebo již přeložené
binárky tak, abyste si je mohli spustit i lokálně.
U Doporučení k zápisu kódu
Tato sekce rozvádí obecné principy zápisu kódu s důrazem na
čitelnost a korektnost. Samozřejmě žádná sada pravidel nemůže
zaručit, že napíšete dobrý (korektní a čitelný) program, o nic více,
než může zaručit, že napíšete dobrou povídku nebo namalujete dobrý
obraz. Přesto ve všech těchto případech pravidla existují a jejich
dodržování má obvykle na výsledek pozitivní dopad.
Každé pravidlo má samozřejmě nějaké výjimky. Tyto jsou ale výjimkami
proto, že nastávají výjimečně. Některá pravidla připouští výjimky
častěji než jiná:
U.0.1 Dekompozice
Vůbec nejdůležitější úlohou programátora je rozdělit problém tak,
aby byl schopen každou část správně vyřešit a dílčí výsledky pak
poskládat do korektního celku.
- Kód musí být rozdělen do ucelených jednotek (kde jednotkou
rozumíme funkci, typ, modul, atd.) přiměřené velikosti, které
lze studovat a používat nezávisle na sobě.
- Jednotky musí být od sebe odděleny jasným rozhraním, které by
mělo být jednodušší a uchopitelnější, než kdybychom použití
jednotky nahradili její definicí.
- Každá jednotka by měla mít jeden dobře definovaný účel, který
je zachycený především v jejím pojmenování a případně rozvedený
v komentáři.
- Máte-li problém jednotku dobře pojmenovat, může to být známka
toho, že dělá příliš mnoho věcí.
- Jednotka by měla realizovat vhodnou abstrakci, tzn. měla by
být obecná – zkuste si představit, že dostanete k řešení
nějaký jiný (ale dostatečně příbuzný) problém: bude Vám tato
konkrétní jednotka k něčemu dobrá, aniž byste ji museli
(výrazně) upravovat?
- Má-li jednotka parametr, který fakticky identifikuje místo ve
kterém ji používáte (bez ohledu na to, je-li to z jeho názvu
patrné), je to často známka špatně zvolené abstrakce. Máte-li
parametr, který by bylo lze pojmenovat
called_from_bar
, je to
jasná známka tohoto problému.
- Daný podproblém by měl být vyřešen v programu pouze jednou –
nedaří-li se Vám sjednotit různé varianty stejného nebo velmi
podobného kódu (aniž byste se uchýlili k taktice z bodu d), může
to být známka nesprávně zvolené dekompozice. Zkuste se zamyslet,
není-li možné problém rozložit na podproblémy jinak.
U.0.2 Jména
Dobře zvolená jména velmi ulehčují čtení kódu, ale jsou i dobrým
vodítkem při dekompozici a výstavbě abstrakcí.
- Všechny entity ve zdrojovém kódu nesou anglická jména.
Angličtina je univerzální jazyk programátorů.
- Jméno musí být výstižné a popisné: v místě použití je
obvykle jméno náš hlavní (a často jediný) zdroj informací
o jmenované entitě. Nutnost hledat deklaraci nebo definici
(protože ze jména není jasné, co volaná funkce dělá, nebo jaký
má použitá proměnná význam) čtenáře nesmírně zdržuje.22
- Jména lokálního významu mohou být méně informativní: je mnohem
větší šance, že význam jmenované entity si pamatujeme, protože
byla definována před chvílí (např. lokální proměnná v krátké
funkci).
- Obecněji, informační obsah jména by měl být přímo úměrný jeho
rozsahu platnosti a nepřímo úměrný frekvenci použití: globální
jméno musí být informativní, protože jeho definice je „daleko“
(takže si ji už nepamatujeme) a zároveň se nepoužívá příliš
často (takže si nepamatujeme ani to, co jsme se dozvěděli, když
jsme ho potkali naposled).
- Jméno parametru má dvojí funkci: krom toho, že ho používáme
v těle funkce (kde se z pohledu pojmenování chová podobně jako
lokální proměnná), slouží jako dokumentace funkce jako celku.
Pro parametry volíme popisnější jména, než by zaručovalo jejich
použití ve funkci samotné – mají totiž dodatečný globální
význam.
- Některé entity mají ustálené názvy – je rozumné se jich držet,
protože čtenář automaticky rozumí jejich významu, i přes
obvyklou stručnost. Zároveň je potřeba se vyvarovat použití
takovýchto ustálených jmen pro nesouvisející entity. Typickým
příkladem jsou iterační proměnné
i
a j
.
- Jména s velkým rozsahem platnosti by měla být také
zapamatovatelná. Je vždy lepší si přímo vzpomenout na jméno
funkce, kterou právě potřebuji, než ho vyhledávat (podobně jako
je lepší znát slovo, než ho jít hledat ve slovníku).
- Použitý slovní druh by měl odpovídat druhu entity, kterou
pojmenovává. Proměnné a typy pojmenováváme přednostně
podstatnými jmény, funkce přednostně slovesy.
- Rodiny příbuzných nebo souvisejících entit pojmenováváme podle
společného schématu (
table_name
, table_size
, table_items
–
nikoliv např. items_in_table
; list_parser
, string_parser
,
set_parser
; find_min
, find_max
, erase_max
– nikoliv
např. erase_maximum
nebo erase_greatest
nebo max_remove
).
- Jména by měla brát do úvahy kontext, ve kterém jsou platná.
Neopakujte typ proměnné v jejím názvu (
cars
, nikoliv
list_of_cars
ani set_of_cars
) nemá-li tento typ speciální
význam. Podobně jméno nadřazeného typu nepatří do jmen jeho
metod (třída list
by měla mít metodu length
, nikoliv
list_length
).
- Dávejte si pozor na překlepy a pravopisné chyby. Zbytečně
znesnadňují pochopení a (zejména v kombinaci s našeptávačem)
lehce vedou na skutečné chyby způsobené záměnou podobných ale
jinak napsaných jmen. Navíc kód s překlepy v názvech působí
značně neprofesionálně.
22 Nejde zde pouze o samotný fakt, že je potřeba něco vyhledat. Mohlo by se zdát, že tento problém řeší IDE, které nás umí „poslat“ na příslušnou definici samo. Hlavní zdržení ve skutečnosti spočívá v tom, že musíme přerušit čtení předchozího celku. Na rozdíl od počítače je pro člověka „zanořování“ a zejména pak „vynořování“ na pomyslném zásobníku docela drahou operací.
U.0.3 Stav a data
Udržet si přehled o tom, co se v programu děje, jaké jsou vztahy
mezi různými stavovými proměnnými, co může a co nemůže nastat, je
jedna z nejtěžších částí programování.
TBD: Vstupní podmínky, invarianty, …
U.0.4 Řízení toku
Přehledný, logický a co nejvíce lineární sled kroků nám ulehčuje
pochopení algoritmu. Časté, komplikované větvení je naopak těžké
sledovat a odvádí pozornost od pochopení důležitých myšlenek.
TBD.
U.0.5 Volba algoritmů a datových struktur
TBD.
U.0.6 Komentáře
Nejde-li myšlenku předat jinak, vysvětlíme ji doprovodným
komentářem. Čím těžší myšlenka, tím větší je potřeba komentovat.
- Podobně jako jména entit, komentáře které jsou součástí kódu
píšeme anglicky.23
- Případný komentář jednotky kódu by měl vysvětlit především „co“
a „proč“ (tzn. jaký plní tato jednotka účel a za jakých
okolností ji lze použít).
- Komentář by také neměl zbytečně duplikovat informace, které jsou
k nalezení v hlavičce nebo jiné „nekomentářové“ části kódu –
jestli máte například potřebu komentovat parametr funkce,
zvažte, jestli by nešlo tento parametr lépe pojmenovat nebo
otypovat.
- Komentář by neměl zbytečně duplikovat samotný spustitelný kód
(tzn. neměl by se zdlouhavě zabývat tím „jak“ jednotka vnitřně
pracuje). Zejména jsou nevhodné komentáře typu „zvýšíme
proměnnou i o jedna“ – komentář lze použít k vysvětlení proč
je tato operace potřebná – co daná operace dělá si může kažďý
přečíst v samotném kódu.
23 Tato sbírka samotná představuje ústupek z tohoto pravidla: smyslem našich komentářů je naučit Vás poměrně těžké a často nové koncepty, a její cirkulace je omezená. Zkušenost z dřívějších let ukazuje, že pro studenty je anglický výklad značnou bariérou pochopení. Přesto se snažte vlastní kód komentovat anglicky – výjimku lze udělat pouze pro rozsáhlejší komentáře, které byste jinak nedokázali srozumitelně formulovat. V praxi je angličtina zcela běžně bezpodmínečně vyžadovaná.
U.0.7 Formální úprava
TBD.