Programování pro fyziky 2023/24 – přednáška č. 5

Na předchozí přednášce jsme pokročili s imperativním strukturovaným programováním, podívali se na obvyklé příkazy pro vstup a výstup dat a ukázali si obecný aparát operačních systémů pro vstup a výstup dat do souborů. Dnes se zahloubáme do vlastností standardních datových typů a budeme s jejich využitím konstruovat výrazy.

Standardní datové typy

Datový typ charakterizuje vlastnosti proměnných, konstant a dalších veličin. Několik datových typů reprezentujících celá čísla a reálná čísla je standardizováno a v soudobém hardwaru zakódováno, a je proto standardně zpřístupněno v programovacích jazycích. Pro pohodlí programátora jazyky poskytují i různá, rovněž standardní, rozšíření nebo omezení těchto typů (komplexní, logické a znakové typy). Někdy se této pětici standardních typů říká primitivní typy.

Celočíselné typy (int, integer)

Jsou vhodné pro reprezentaci matematicky celočíselných hodnot. V paměti mívají vyčleněnou velikost obvykle 4 byty (32 bitů) a dovolují tak udržet celkem \(2^{32}\) různých celočíselných hodnot; jazyky běžně nabízejí i jiné velikosti (1, 2 a 8 B). Rozsah hodnot může pokrývat přirozená čísla (neznaménkový integer) nebo častěji čísla po obou stranách nuly (znaménkový integer). Velikost v paměti implikuje rozsah dostupných hodnot v desítkové soustavě: 1bytový (8bitový) znaménkový typ zahrnuje \(2^8\) = 256 různých hodnot \(-2^7\).. \(2^7-1\) čili -128..127, 2bytový typ snese \(-2^{15}\).. \(2^{15}-1\) čili -32768..32767 a 4bytový typ \(-2^{31}\).. \(2^{31}-1\) čili něco přes ±2 miliardy. V Pythonu se tyto hardwarové celočíselné typy dostanou na světlo balíčkem NumPy, jinak je typ int softwarově emulovaný bez velikostního omezení.

Celočíselné literály (doslovné hodnoty) jsou tvořeny číslicemi a volitelným znaménkem: 0, 1, -2, +3 apod. Mohou se objevit i podtržítka nebo písmenné přípony: v Pythonu pro zvýšení čitelnosti, 1_000_000, ve Fortranu pro zadání velikosti literálu v bytech, 1_8, v C písmeno L (long), 1L. Default velikost celočíselného literálu bývá 4 B, v 64bitových systémech často 8 B. Jazyky umožňují zápis literálů i v šestnáctkové a dalších soustavách, např. v Pythonu lze psát pomocí literálů a konverzních funkcí 15,0b1111,0o17,0xf, int('15',10),int('1111',2),int('17',8),int('f',16), a výpis hodnot v těchto soustavách zajišťují formátové specifikace nebo konverzní funkce print(f'{255:b}','%o'%255,'%x'%255,bin(255),oct(255),hex(255)).

Meze celočíselných typů sdělí v NumPy struktura np.iinfo, posun nad horní reprezentovatelnou mez se v tichosti projeví přesunem k dolní mezi (integer overflow nevyvolává hardwarovou výjimku, může však být hlídáno softwarově). Neomezený typ int v Pythonu dovoluje jít mnohem výše: 2**64,10**100.

Interní reprezentace celých čísel. Uložení celých čísel v paměti připomíná zápis v dvojkové soustavě, např. \(0101 = 2^2+2^0 = 5\). Bity \(f_k\) o hodnotách 0 nebo 1 se pro \(p\)-bitový neznaménkový integer interpretují podle vzorce \(\sum_{k=1}^p f_k\cdot2^{k-1}\) a pro \(p\)-bitový znaménkový integer při \(f_p=0\) jako nezáporná čísla \(\sum_{k=1}^{p-1} f_k\cdot2^{k-1}\) a při \(f_p=1\) jako záporná čísla \(-2^{p-1}+\sum_{k=1}^{p-1}f_k\cdot2^{k-1}\). V příkladu výše jsou číslice \(f_k\) zapsány po řadě zprava doleva. Viz také reprezentace celých čísel na Wikipedii a jinde.

# Python a NumPy: celočíselné datové typy
import numpy as np
print(1,int(1),np.int_(1),np.int32(1),np.int64(1))
print(15,0b1111,0o17,0xf, int('15',10),int('1111',2),int('17',8),int('f',16))  # vstup int v desítkové, dvojkové, osmičkové a šestnáctkové soustavě
print(f'{255:b}','%o'%255,'%x'%255,bin(255),oct(255),hex(255))  # výstup int v různých soustavách

MAX4=np.int32(np.iinfo(np.int32).max); MAX8=np.int64(np.iinfo(np.int64).max)
print(MAX4,MAX8);  # print(MAX4+1,MAX8+1)  # np.int32 a np.int64 overflow v NumPy
print(10_000_000_000,2**64,10**100)        # neomezený int v Pythonu
! Fortran: celočíselné datové typy
print '(7(i0,x))',1,1_4,1_8,1_16,int(1,4),int(1,8),int(1,16)
n10=15; n2=int(b'1111'); n8=int(o'17'); n15=int(z'f')  ! vstup: boz literaly
print '(4(i0,x))',n10,n2,n8,n15
print '(b0,x,o0,x,z0)',255,255,255                     ! vystup: formatove specifikace

print *,huge(1),huge(1_8),huge(1_16);  ! print *,huge(1)+1  ! 4bytove integer overflow
end program

Reálné typy (float, real)

Jsou vhodné k aproximaci reálných čísel. V paměti zabírají buď 4 byty – single precision nebo (nejčastěji) 8 bytů – double precision, (někdy) 10 bytů – extended precision a (zřídka) 16 bytů – quadruple precision, poslední dobou (pro GPU) i 2 byty – half precision. Reprezentovaná čísla mají omezenou přesnost mantisy a omezený rozsah exponentu, zachycují tedy jen některá reálná, přesněji racionální čísla, a někdy se jim raději než reálná říká floating point, čísla s plovoucí (binární) tečkou. Python pracuje jen s 8bytovým typem float, NumPy k ekvivalentnímu np.double doplňuje 4bytový np.single a další.

Reálné literály obsahují desetinnou tečku nebo v semilogaritmickém tvaru o desítkovém základu symbol exponentu: 0., 1.1, -0.2, 1e0, +1.e+0, 3.14e0, 6.67e-11 (pro \(6.67\times10^{-11}\)). V Pythonu, C a Matlabu je jejich default přesnost 8 B, ve Fortranu 4 B. V C vyznačuje přípona f float literály a L long double literály (tj. větší než double, pokud lze), Fortran používá podtržítka: 1._8, 1._16.

Interní reprezentace reálných čísel. Podobu počítačových reálných čísel ukotvila norma IEEE 754, v procesorech respektovaná. Popisuje čísla v semilogaritmickém tvaru o dvojkovém základu \(\displaystyle (-1)^s\cdot M\cdot 2^E\), kde \(s\) je znaménkový bit o hodnotách 0 nebo 1, mantisa je definována binární řadou \(\displaystyle M=1+\sum_{k=1}^{p-1} f_k\cdot \left(\frac12\right)^{k}\) a \(E\) je celočíselný exponent v mezích \(\langle E_{\min},E_{\max}\rangle\). Bity \(f_k\) nabývají hodnot 0 nebo 1, \(f_0\) je vždy 1 a mantisa je normalizovaná, \(1\le M<2\). Přesnost mantisy a rozsah exponentu jsou definovány v dvojkové soustavě, člověk je pozoruje v desítkové soustavě:

real

celkem

mantisa

exponent

precision

bytů

bitů

p bitů

epsilon

platných míst

dvojkově

desítkově

single

4

32

24

\(2^{-23}\ \approx1\cdot10^{-7}\)

7,22

–126..127

–45..38

double

8

64

53

\(2^{-52}\ \approx2\cdot10^{-16}\)

15,95

–1022..1023

–324..308

double extended

10

80

64

\(2^{-63}\ \approx1\cdot10^{-19}\)

19,27

–16382…16383

–4951..4932

quadruple

16

128

113

\(2^{-112} \approx2\cdot10^{-34}\)

34,02

–16382…16383

–4951..4932

Relativní přesnost (též počítačové epsilon) chápeme jako nejmenší číslo, jehož přičtením lze změnit jedničku, a při \(p\)-bitové mantise se rovná \(2^{1-p}\). Počtem platných míst v desítkové soustavě pak myslíme hodnotu \(P=p\log_{10} 2\) plynoucí ze vztahu \(10^{-P}=2^{-p}\). Největší single hodnota je \(\sum_{k=0}^{23}(\frac12)^k\cdot2^{127}\), přibližně 3.4e38, a pro double \(\sum_{k=0}^{52}(\frac12)^k\cdot2^{1023}\) ~ 1.8e308, nejmenší řádnou kladnou hodnotou je single \(1\cdot2^{-126}\) ~ 1.2e-38 a double \(1\cdot2^{-1022}\) ~ 2.2e-308. Lze pracovat i s čísly s menší než řádnou kladnou hodnotou, tj. s \(f_0=0\) (tzv. subnormální nebo denormalizovaná čísla). Nejmenším kladným z nich je v single přesnosti \((\frac12)^{23}\cdot2^{-126}\) ~ 1.4e-45, v double \((\frac12)^{52}\cdot2^{-1022}\) ~ 4.9e-324. Viz také reprezentace reálných čísel na Wikipedii a konvertor.

Běžně se počítá v 8bytové přesnosti s 16místnou dekadickou mantisou, přičemž procesory mohou interně vyčíslovat ve vyšší přesnosti (proto má mantisa 10bytového typu právě 64 bitů). 16bytová přesnost s 34místnou mantisou se užívá jen výjimečně, bývá softwarově emulovaná a velmi pomalá, pokud je vůbec překladačem nabízena. S rozmyslem se musí zacházet se 4bytovou přesností o 7místné mantise, což leckdy numerickým algoritmům nestačí.

Ukázka: flintmax aneb největší z nepřetržité řady celých čísel přesně reprezentovatelných v reálné single precision (Matlab: format long; flintmax('single')). Kladná celá čísla lze v reálných typech dlouho reprezentovat přesně, ale nakonec to musí narazit – v np.single někde nad 7 platnými dekadickými místy, konkrétně u :math:2^{24}, viz následující výpis a konec ukázky níže: print(2**24,16777216,int(np.single(16777217))) vypíše třikrát 16777216.

Přetečení, podtečení a uzavřenost. Rozsah exponentů uvedený v tabulce (vždy mocnina 2 minus 2) napovídá, že 2 hodnoty exponentu jsou rezervovány pro speciální hodnoty. Patří k nim dvě znaménková nekonečna (+Inf, –Inf) pro čísla s příliš velkou absolutní hodnotou, dvě znaménkové nuly, subnormální čísla čili nenulová čísla s \(f_0=0\) a tzv. nečísla (NaN, Not-a-Number), kterými se nahrazují matematicky nedefinované výsledky, např. 0./0., np.sqrt(-1.) apod. Výsledky operací s reálnými operandy tak mohou zůstat v oboru reálného typu, reálná aritmetika je uzavřená.

Na závěr paradox: celých čísel je více než reálných čísel. Přesněji – celočíselný datový typ reprezentuje více různých číselných hodnot než reálný typ o témže počtu bytů, už jen kvůli existenci dvojice reálných nul, hlavně však nečísel.

# Python a NumPy: reálné datové typy
import numpy as np
import warnings
warnings.filterwarnings('ignore')     # potlačíme zbytná varování

print('1...',1.,1e0,float(1))                # pythonský float
print(np.single(1),np.double(1),np.half(1),np.longdouble(1)) # NumPy reálné typy
print(type(np.single(1)),type(np.double(1)),type(np.half(1)),type(np.longdouble(1)))
print('mix...',0., 1.1, -0.2, 1e0, +1.e+0, 3.14e0, 6.67e-11)

print('int nula...',+0,-0,+0==-0,id(+0)==id(-0))
print('float nuly...',+0.,-0.,+0.==-0.,id(+0.)==id(-0.))

MAX4=np.finfo(np.single).max; MAX8=np.finfo(np.double).max
print('max...',MAX4,MAX8)             # největší reprezentovatelná čísla v single a double precision
print('more...',MAX4+MAX4,MAX8+MAX8)  # np.single a np.double overflow v NumPy

eps4=np.finfo(np.single).eps; eps8=np.finfo(np.double).eps; epsL=np.finfo(np.longdouble).eps
print('eps...',eps4,eps8,epsL)        # počítačové epsilon (nejmenší číslo ovlivňující přičtením jedničku)
eps=1
while 1+eps*0.5>1: eps=eps*0.5        # výpočtem pro float
print('eps...',eps)
print(1+eps4*0.5>1,1+eps4>1, 1+eps8*0.5>1,1+eps8>1, 1+epsL*0.5>1,1+epsL>1)  # eps v akci

print('zero division...')
# for n in range(-1,2): print(n,0/n,1/n)    # ZeroDivisionError: division by zero
for n in np.arange(-1,2): print(n,0/n,1/n)  # ... 0 nan inf ...
print('sqrt...',np.sqrt(-1),np.sqrt(-1+0j))           # nan 1j

print(2**24,16777216,int(np.single(16777217)))
for n in range(100_000_000):
if n!=int(np.single(n)): break      # nejmenší celé číslo nereprezentovatelné v np.single
print('flintmax+1 =',n)

Komplexní typy (complex)

Jsou vhodné k aproximaci komplexních čísel. Interně jsou reprezentovány dvěma čísly reálného typu pro reálnou a imaginární část a sdílejí s nimi vlastnosti jako přesnost mantisy a rozsah exponentu. Komplexní aritmetika je softwarovou nadstavbou nad hardwarovou reálnou aritmetikou. Komplexní literál v Pythonu je ve tvaru součtu reálné a imaginární části doplněné příponou j, např. 1+0j, ve Fortranu v podobě páru (1,0). Jazyky preferují udržování výsledků matematických funkcí s reálnými argumenty v reálném oboru, takže např. výpočet \(\sqrt{-1}\) voláním funkce sqrt(-1.) zpravidla vede k chybě (math.sqrt: math domain error) nebo NaN (np.sqrt). Python má proto balíček cmath s komplexní aritmetikou a NumPy funkce se přizpůsobí, dostanou-li argument komplexního typu.

# Python a NumPy: komplexní datové typy
import math
import cmath
import numpy as np
import warnings
warnings.filterwarnings('ignore')       # potlačíme zbytná varování

print('1...',1+0j,1.+0.j,complex(1,0))  # pythonský complex
j=1; print(1+j)                         # int výraz, nikoliv complex literál
print(np.csingle(1),np.cdouble(1),np.clongdouble(1))  # NumPy komplexní typy

MAX8=np.finfo(np.double).max
print('max in complex...',complex(MAX8,MAX8))
print('inf in complex...',complex(MAX8+MAX8,MAX8),complex(MAX8,MAX8+MAX8),MAX8+MAX8+MAX8*1j)
print('nan in complex...',complex(np.double(1)/0,np.double(0)/0))

x=-1.
# print(math.sqrt(x))  # math domain error
print(np.sqrt(x))
c=-1+0j; c=complex(-1); c=-1; c=cmath.sqrt(c); print(c,' ** 2 =',c**2)  # cmath.sqrt
c=-1+0j; c=np.sqrt(c); print(c,' ** 2 =',c**2)                          # np.sqrt
! Fortran: komplexní datové typy
real(8) :: MAX8,one=1.,zero=0.
complex(8) c

print *,'1...',(1,0),(1.,0.),cmplx(1,0,8)        ! fortranský complex
print *,cmplx(1,0,4),cmplx(1,0,8),cmplx(1,0,16)  ! komplexní typy

MAX8=huge(1._8)
print *,'max in complex...',cmplx(MAX8,MAX8)
print *,'inf in complex...',cmplx(MAX8+MAX8,MAX8),cmplx(MAX8,MAX8+MAX8)
print *,'nan in complex...',cmplx(one/0,zero/0)

c=-1; c=sqrt(c); print *,c,' ** 2 =',c**2  ! complex sqrt
end program

Logické typy (bool, logical)

Jsou vhodné pro reprezentaci logických hodnot pravda/nepravda. Ty se nejčastěji objevují při rozhodování v podmíněných příkazech jako výsledky relačních výrazů. V Pythonu se logický typ nazývá bool (literály: True, False) a lze jej po vzoru jazyka C nahradit číselnými hodnotami (0 pro False, nenula pro True). Fortran má typ logical (literály: .true., .false.).

Př. Větvení podle podmínky. Opakujeme zkrácený podmíněný příkaz if a podmíněný výraz:

b=True                                # Python
if b: print(b)                        # True
b=1; print('true' if b else 'false')  # true
logical b                             ! Fortran
b=.true.; if (b) print *,b            ! T
print *,merge('true ','false',b)      ! true

Znakové typy (str, character)

Jsou určeny pro reprezentaci znaků, v programovacích jazycích často 1bytových (v kódování podle tabulky ASCII), ale i vícebytových (kódování UTF-8 či Unicode). 1bytové kódování umožňuje reprezentovat 256 znaků, z nichž pevně daná první polovina (tzv. 7bitové ASCII kódy 0..127) zahrnuje 10 číslic, 2krát 26 znaků pro velká a malá písmena bez diakritiky a několik desítek dalších symbolů (mezera, !, '', # atd., nikoliv však např. , , ); druhá polovina tabulky se znaky národních abeced je přepínatelná. Dá se zapamatovat několik ASCII kódů a v příhodné okamžiky jich využít: souvisle řazené číslice začínají na kódu 32*3/2 (např. jednička na 48+1), velká písmena na 32*2+1 a malá písmena na 32*3+1. Ve Windows přežívá starodávný postup pro zadávání znaků pomocí jejich ASCII kódů: podržet klávesu Alt, na numerické klávesnici (klávesy vpravo, ne nahoře) zadat kód a uvolnit Alt (např. Alt+64 pro @, Alt+92 pro \).

Znakový typ v Pythonu je str, ve Fortranu character, v C char, ale jde o 1bytový celočíselný typ (pro ASCII kód znaku). Znakové literály se uzavírají mezi apostrofy, př. 'a', někdy jsou povoleny i uvozovky, "a"; literál obsahující apostrof se píše někde zdvojeně, '''', jinde pomocí tzv. escape sekvence, '\''. Ke znakovým literálům lze řadit i prázdný řetězec ''. Jméno chr či char typicky nese i konverzní funkce ASCII kódu na příslušný znak.

Př. ASCII tabulka. Třířádkový výpis klíčové části tabulky se znaky s kódy 33-64, 65-96, 97-128:

# 33- 63... !"#$%&'()*+,-./0123456789:;<=>?@
# 65- 96... ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`
# 97-128... abcdefghijklmnopqrstuvwxyz{|}~⌂Ç

# Python
for row in range(1,4):
  for col in range(1,33):
    print(chr(row*32+col),end='')
  print()

! Fortran
integer r,c; do r=1,3; do c=1,32; print '(a$)',char(r*32+c); enddo; print *; enddo

// C
int r,c; for (r=1; r<=3; r++) { for (c=1; c<=32; c++) { printf("%c",r*32+c); } printf("\n"); }

% Octave
for row=1:3, disp([char((1:32)+row*32)]); end

Výrazy

Výraz je předpis pro získání hodnoty vyhodnocením operací kombinujících operandy (proměnné, konstanty, návratové hodnoty funkcí) pomocí operátorů. Výraz lze použít na pravé straně přiřazovacího příkazu, jako argument při volání procedury, jako součást jiného výrazu a na dalších místech. Aritmetické výrazy se skládají z matematických operací na numerických operandech, řetězcové výrazy skládají (řetězí) řetězce, v relačních výrazech jsou operandy porovnávány, v logických výrazech se operandy kombinují podle pravidel Booleovy algebry. Pro vyhodnocování složitějších výrazů jsou stanoveny priority operátorů určující pořadí, v jakém se vyhodnocují dílčí výrazy. Není-li v jazyce k dispozici jinak běžný operátor, je patrně realizován funkcí.

Aritmetické výrazy

Elementární operace zahrnují sčítání +, odčítání -, násobení *, dvě dělení / a // s % (Python: reálné dělení a pro celočíselné dělení podíl se zbytkem) a umocňování ** nebo (Matlab/Octave ) ^. Jejich operandy i výsledná hodnota jsou celočíselného, reálného nebo komplexního typu. Datový typ výsledku se často snaží udržet datový typ operandů: jsou-li operandy celočíselné, výsledek je celočíselný (s možnou výjimkou dělení), jsou-li operandy reálné, výsledek je reálný (nekonečna a nečísla patří mezi reálná čísla) a podobně pro operandy komplexní.

# Python: elementární operace
print(1+1,1.+1.,1+0j+1+0j)      # int 2, float 2.0, complex (2+0j)
print(1*2,1.*2.,(1+0j)*(2+0j))  # int 2, float 2.0, complex (2+0j)
print(5./3.,5//3,5%3)           # 1.6666666666666667 1 2
print(2**2,2.**2.)              # 4 4.0

Typová konverze. Jsou-li operandy různého typu, provede se nejprve implicitní typová konverze, konkrétněji povýšení typu operandu o nižším typu, přičemž celočíselné typy se chápou jako nižší než reálné a ty jako nižší než komplexní. Výsledek je pak téhož typu jako operandy po typové konverzi, s výjimkou dělení. Atypické mohou být i konverze při umocňování. (Navíc, interpretovat malý celočíselný exponent pomocí násobení je vhodnější než povýšit jej na reálný a vyčíslovat logaritmy.)

# Python: implicitní typové konverze
print(1.+1,1+1.,1.+1.)          # 2.0 2.0 2.0
print(1.*2,1*2.,2*(1+1j))       # 2.0, 2.0, complex (2+2j)
print(5/3,5.//3.,5.%3.)         # 1.6666666666666667 1.0 2.0
print(2**2,2.**2,2**-2,2.**-2)  # 4 4.0 0.25 0.25

Komutativita a asociativita. Počítačové operace + a * jsou jako v matematice komutativní, platí: 0+1 je totéž co 1+0, 0.*1 je 1*0., huge(1)+1 je 1+huge(1) (tj. integer overflow) a 1e200*1e300 je 1e300*1e200 (tj. +Inf). Operace + a * však někdy nejsou asociativní, a to jako důsledek omezeného rozsahu a omezené přesnosti reálných typů: (1e300*1e300)*1e-300 je +Inf, zatímco 1e300*(1e300*1e-300) je 1e300, a (-1.0+1.0)+1e-16 je 1e-16, zatímco -1.0+(1.0+1e-16) je 0.0.

Dělení. V programovacích jazycích bývá dvojí dělení, reálné s reálným výsledkem (a od něj odvozené komplexní dělení s komplexním výsledkem) a celočíselné s celočíselnými operandy i výsledkem (tj. s celou částí matematického výsledku). Pythonské operátory / a // s % jsme už zmínili. Některé jazyky (Fortran, C) mají pro dělení jen jeden operátor / a rozhodují se kontextově podle typu operandů – jsou-li oba operandy celočíselné, jde o dělení celočíselné, jinak se dělí v reálném, resp. komplexním typu. Pro zbytek po celočíselném dělení čili modulo nemá Fortran operátor, ale funkci mod. Python: 1/2 i 1./2. jsou 0.5, 1//2 je 0 a 1%2 je 1. Fortran a C: 1/2 je integer 0 a 1./2, 1/2. a 1./2. jsou real 0.5. Komplexní dělení (Python) 1/(1+1j) vrací (0.5-0.5j), resp. (Fortran) 1/(1,1) bude (0.5,-0.5).

Rychlost. Elementární operace +, - a * mají vysokou rychlost, odpovídá frekvenci procesoru udávané v GHz, za sekundu se jich tedy stihnou řádově miliardy. Dělení bývá několikanásobně pomalejší. Umocnění (s možnou výjimkou situace s malým celočíselným exponentem) sdílí náročnost výpočtu s logaritmem a exponenciálou, je řádově pomalejší než elementární operace.

Priority. Různé matematické operace mohou mít rozdílnou prioritu: umocnění má obvykle nejvyšší prioritu, pak přijdou na řadu multiplikativní operace (násobení a dělení) a nakonec aditivní operace (sčítání a odčítání). Pro více operací stejné priority může být stanoveno pořadí vyhodnocování, obvykle zleva doprava, s obvyklou výjimkou (zprava doleva) pro umocňování (nikoliv však v Matlabu). Požadované pořadí vyhodnocování lze zvýraznit závorkami.

# Python: priority aritmetických operátorů
print(1+2*3,(1+2)*3,1+(2*3))        # 7 9 7
print(2**3**2,(2**3)**2,2**(3**2))  # 512 64 512

Kontextový operátor dělení vyžaduje při psaní výrazů zvláštní pozornost. Může být použit i při interpretaci umocňování na záporný exponent.

! Fortran: strašidelné příklady uplatnění priorit s kontextovým dělením
print *,1/3+1/3+1/3, 1./3+1/3+1/3, 1./3+1./3+1./3  ! 0 0.333333343 1.00000000
print *,1/4*4,       1/4*4.,       1./4*4          ! 0 0.000000000 1.00000000
print *,10**(-1),                  10.**(-1)       ! 0 0.100000001

Matematické funkce. Pro numerické počítání potřebujeme víc než jen několik matematických operátorů. Další matematické funkce jsou shromážděny v balíčcích math, cmath a numpy.

# Python: matematické funkce knihoven math a numpy
from math import *
# from numpy import *
print(int(1.5),round(1.5),trunc(1.5),floor(1.5),ceil(1.5),min(1,2),max(1.,2.))
x=3.14; print(copysign(1.,x),abs(-1),abs(1+1j),abs(complex(1,1)),hypot(1.,1.))
# print(4%3,modulo(4,3),mod(-4,3),modulo(-4,3))
print(sqrt(2),log(exp(1)),log10(10**1),asin(sin(1)),asinh(sinh(1)),atan(1)*4)
print(pi,e,inf,nan)
print(comb(3,1),gcd(6,9),lcm(6,9))
nmax=10; print(sum(range(1,2*nmax,2)),prod(range(1,nmax+1)),factorial(nmax))
! Fortran: matematické funkce
print *,int(1.5),nint(1.5),floor(1.5),ceiling(1.5),min(1,2),max(1.,2.)
x=3.14; print *,sign(1.,x),abs(-1),abs((1,1)),abs(cmplx(1,1)),hypot(1.,1.)
print *,mod(4,3),modulo(4,3),mod(-4,3),modulo(-4,3)
print *,sqrt(2.),log(exp(1.)),log10(10.**1),asin(sin(1.)),asinh(sinh(1.)),atan(1.)*4
x=0; print *,atan(1._8)*4,exp(1._8),1/x,0/x
nmax=10; print *,sum([(n,n=1,2*nmax,2)]),product([(n,n=1,nmax)])
end program

Řetězcové výrazy

Pro práci s daty znakových typů bývají k dispozici jen operátory pro řetězení („sčítání“, případně „násobení“) znaků a řetězců: (Python) + a *, (Fortran) // (pro sčítání). Složitější práce s daty znakových typů se koná pomocí funkcí a objektových metod.

# Python: řetězení řetězce
n=3; print('A'+'BC','A'*3,'B'*n)
# řezání řetězce
print('IJ'[0],'IJKLMN'[:2],'IJKLMN'[-2:],'IJKLMN'[::-1],'XYZ   '.strip())
# délka řetězce, poloha podřetězce
print(len('ABC'),len('ABC   '.strip()),'ABCDE'.find('CD'),'ABCDE'.index('CD'))
# počet výskytů podřetězce, náhrada podřetězce, převody velikosti
print('ABBCCC'.count('B'),'ABC'.replace('B','*'),'AbC'.lower(),'AbC'.swapcase(),'AbC'.upper())
! Fortran: retezeni retezce
n=3; print '(*(a,x))','A'//'BC',repeat('A',3),repeat('B',n)
! rezani retezce
print '(*(a,x))','IJ'(1:1),'IJKLMN'(:2),'IJKLMN'(5:),trim('XYZ   ')
! delka retezce, poloha podretezce
print *,len('ABC'),len_trim('ABC  '),index('ABCDE','CD')
end program

V Matlabu/Octavu je 'A'+'B' rovno 131 (součet ASCII kódů 65+66).

Relační výrazy

Relace slouží k porovnání operandů kompatibilních typů. Vždy lze porovnávat na rovnost a nerovnost, pro operandy datových typů, pro které je definováno pořadí, lze porovnávat i velikost. (Standardní datové typy, s obvyklou výjimkou pro typ complex, mají definováno pořadí.) Výsledkem bývá hodnota logického typu, v některých jazycích celočíselná hodnota s logickou interpretovatelností. Pro test rovnosti se užívá dvojznak ==, operátor pro nerovnost je mnohotvárnější: != (Python, C, Octave), /= (Fortran), <> (Pascal). Operátory pro porovnání velikosti jsou jednoznačné: >, >=, <, <=. Fortran má i ekvivalentní zkratky: .eq., .ne., .gt., .ge., .lt., .le..

# Python: relační výrazy
print(1==1, 1!=2, 1<2, 1.<=2., 1<2., False<True, '1'<'2')  # 7krát True

Test rovnosti v reálné aritmetice. Zvýrazněme nevhodnost až nebezpečnost zjišťování přesné rovnosti dvou reálných operandů, jsou-li výsledkem nepřesných operací reálné aritmetiky. Vhodné je stanovit si potřebnou přesnost a porovnávat absolutní hodnotu rozdílu reálných operandů s touto přesností. Pythonské balíčky math a numpy mají pro totéž funkci isclose, každý s jiným default nastavením.

# Python: test rovnosti pro float
print(0.1+0.1==0.2)                    # True
print(0.1+0.1+0.1==0.3)                # False
print(abs(0.1+0.1-0.2)<1e-15)          # True
print(abs(0.1+0.1+0.1-0.3)<1e-15)      # True
import math; import numpy
print(math.isclose(0.1+0.1+0.1,0.3))   # True
print(numpy.isclose(0.1+0.1+0.1,0.3))  # True

Logické výrazy

Operandy logického typu lze skládat podle pravidel Booleovy algebry. K dispozici bývají operátory pro negaci (Python: not, jinde např. !), konjunkci neboli logický součin (Python: and, jinde např. &&), disjunkci neboli logický součet (Python: or, jinde např. ||) a exkluzivní disjunkci (Python: ^, jinde např. xor).

a

b

not a

a and b

a or b

a ^ b

not a or b (=>)

False

False

True

False

False

False

True

False

True

True

False

True

True

True

True

False

False

False

True

True

False

True

True

False

True

True

False

True

Bývá využíváno zkrácené vyhodnocování logických výrazů: ve výrazech False and X a True or X není třeba pro zjištění výsledku vyhodnocovat operand X. To však může v některých situacích způsobit odlišné chování programu než při úplném vyhodnocení všech operandů.

Priority

Pravidla pro pořadí vyhodnocování aritmetických výrazů jsme již uvedli. Po vyhodnocení dílčích aritmetických výrazů se obvykle vyhodnocují relace a ty se nakonec poskládají pomocí logických operátorů. Když standardní priority nevyhovují nebo nejsou zřejmé, lze závorkovat.

Př. (Python) 1<2 and 3<4, (Fortran) 1<2 .and. 3<4, (C, Octave) 1<2 && 3<4.

A jiné

Jazyky mívají bitové operátory, operující na integer operandech bit po bitu podle pravidel Booleovy algebry. Je-li v jazyce zaveden datový typ množina, jsou tam i množinové operátory (průnik, sjednocení ad.). Oblíbený bývá podmíněný výraz (jediný ternární, se třemi operandy) vracející podle logické interpretace jednoho operandu hodnotu druhého nebo třetího operandu: (C) 1 ? "ano" : "ne", (Python) 'ano' if 1 else 'ne' pro 'ano'. Vzácností je operátor pro faktoriál: (gnuplot) 5!.

Otázky

V různých jazycích a v různých kontextech mohou existovat různé odpovědi.

  1. Co provede výpis literálu 12345678901234567890?

  2. Co provede výpis literálu 12345678901234567890.?

  3. Co provede výpis literálu 16777217.?

  4. Co provede výpis literálů 00, 007, 010?

  5. Je rozdíl mezi zápisy 0.5 a 1/2, 0.1 a 1/10?

  6. Jak přesné jsou v počítačích reprezentace zlomků 1/2, 1/3, 1/4 a 1/10?

  7. Co je tak jiného na literálech 1e300 a 1e400? (Existují vůbec?)

  8. Co je výsledkem relace +0==-0? Co je výsledkem relace +0.==-0.? (Jsou vůbec operandy různé?)

  9. Co provede volání funkce sqrt(-1)? A sqrt(-1.)? (Jak odmocnit komplexní -1?)

  10. Co je víc, True nebo False? (Co je výsledkem relace True>False?)

  11. Co je víc, A nebo a? (Co je výsledkem relace 'A'>'a'?)

  12. Je (na počítačích) více celých čísel nebo reálných čísel?

  13. Je + a * komutativní?

  14. Je + a * asociativní?

  15. Je 1/3 totéž co 1./3.?

  16. Je 1+2*3 totéž co 1+(2*3)?

  17. Je 2**3**2 rovno 64 nebo 512?

  18. Je 1/3+1/3+1/3 rovno 1? Je 1./3+1/3+1/3 rovno 1? Je 1./3+1./3+1./3 rovno 1?

  19. Je 0.1+0.1=0.2 rovno true? Je 0.1+0.1+0.1=0.3 rovno true?