Datový typ Decimal v programovacím jazyku Python

11. 4. 2024
Doba čtení: 25 minut

Sdílet

 Autor: Depositphotos
Na úterní článek o numerických datových typech Decimal32, Decimal64 a Decimal128 dnes alespoň částečně navážeme. Budeme se totiž zabývat tím, jak je dekadický formát s plovoucí řádovou čárkou realizován v Pythonu.

Obsah

1. Datový typ Decimal v programovacím jazyku Python

2. Základní numerické datové typy programovacího jazyka Python

3. Další numerické datové typy dostupné v knihovnách

4. Od typu float k typu Decimal

5. Konstruktory typu Decimal

6. Přepis počítané programové smyčky

7. Výpočet hodnoty π

8. Problém zastavení výpočtů

9. Výpočet harmonické řady

10. Hodnoty, které nelze uložit přesně

11. Dělení nulou

12. Operace 0/0 a 00

13. Operace s nekonečny

14. Nastavení přesnosti výpočtů i uložení výsledků

15. Změna zaokrouhlovacího režimu

16. Zachytávání výjimek při výpočtech

17. Určení maximálního exponentu a reakce na přetečení hodnoty

18. Příloha: „numerická věž“ v programovacích jazycích

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Datový typ Decimal v programovacím jazyku Python

Na úterní článek o numerických datových typech Decimal32, Decimal64 a Decimal128 dnes alespoň částečně navážeme. Budeme se totiž zabývat tím, jakým způsobem je dekadický formát s plovoucí řádovou čárkou realizován v programovacím jazyku Python. Již v základní knihovně Pythonu totiž nalezneme standardní modul nazvaný příznačně Decimal, který obsahuje implementaci dekadického formátu s plovoucí řádovou čárkou. Ovšem již na úvod je nutné poznamenat, že se v tomto případě nejedná o formáty Decimal32/64/128 tak, jak jsou popsány v normě IEEE 754–2008, protože interní struktura hodnot typu Decimal je odlišná. Ovšem základní myšlenka zůstává zachována – je použit formát s plovoucí řádovou čárkou (tečkou) a exponentem s desítkovou bází, který navíc dokáže pracovat s nekonečny atd.

2. Základní numerické datové typy programovacího jazyka Python

Připomeňme si, že v programovacím jazyku Python existují tři základní numerické datové typy, které jsou „vhodně“ pojmenovány takovým způsobem, že mohou mnohé programátory poměrně úspěšně zmást. Jedná se o datové typy int, float a complex. Numerický datový typ int umožňuje práci s celočíselnými typy s prakticky neomezeným rozsahem, na rozdíl od podobně pojmenovaného typu v jiných jazycích (C, C++, Java, Go, …), kde se jedná o celočíselný typ s předem specifikovaným omezeným rozsahem (například 32bitů či 64bitů). Druhý standardní numerický datový typ se v Pythonu jmenuje float, což může být opět matoucí, protože interně se jedná o céčkovský typ double. A konečně numerický typ complex sestává z dvojice hodnot typu float, které reprezentují reálnou a imaginární složku komplexního čísla. Komplexní jednotka se v Pythonu zapisuje znakem j a nikoli i:

>>> type(10)
<class 'int'>
 
>>> type(10**10000000)
<class 'int'>
>>> type(3.14)
<class 'float'>
 
>>> type(1+2j)
<class 'complex'>
 
>>> type(1+2i)
  File "<stdin>", line 1
    type(1+2i)
SyntaxError: invalid decimal literal

3. Další numerické datové typy dostupné v knihovnách

Kromě základní trojice typů popsaných v předchozí kapitole je však možné ve standardní knihovně Pythonu nalézt realizaci dvou dalších numerických datových typů. Jedná se o typ nazvaný Decimal a o typ Fraction (pozor, je umístěný v modulu fractions, tedy s „s“ na konci). V dnešním článku nás bude primárně zajímat především typ Decimal, i když si ukážeme, že typ Fraction je s Decimal v některých ohledech poměrně úzce propojen.

V některých oblastech IT je ovšem nutné pracovat i s dalšími numerickými datovými typy. V takovém případě může být vhodné využít například možnosti nabízené knihovnou NumPy, v níž nalezneme podporu pro tyto datové typy, a to včetně typu half (precision), se kterým se začínám setkávat poměrně často (popř. i s typem bfloat16):

╔════════════╤═══════════════════════════╤═══════════════════════════════╗
║ Formát     │ Popis                     │ Rozsah                        ║
╟────────────┼───────────────────────────┼───────────────────────────────╢
║ bool       │ uloženo po bajtech        │  True/False                   ║
╟────────────┼───────────────────────────┼───────────────────────────────╢
║ int8       │ celočíselný se znaménkem  │ -128..127                     ║
║ int16      │ celočíselný se znaménkem  │ -32768..32767                 ║
║ int32      │ celočíselný se znaménkem  │ -2147483648..2147483647       ║
║ int64      │ celočíselný se znaménkem  │ -9223372036854775808..        ║
║            │                           │  9223372036854775807          ║
╟────────────┼───────────────────────────┼───────────────────────────────╢
║ uint8      │ celočíselný bez znaménka  │  0..255                       ║
║ uint16     │ celočíselný bez znaménka  │  0..65535                     ║
║ uint32     │ celočíselný bez znaménka  │  0..4294967295                ║
║ uint64     │ celočíselný bez znaménka  │  0..18446744073709551615      ║
╟────────────┼───────────────────────────┼───────────────────────────────╢
║ float16    │ plovoucí řádová čárka     │  poloviční přesnost (half)    ║
║ float32    │ plovoucí řádová čárka     │  jednoduchá přesnost (single) ║
║ float64    │ plovoucí řádová čárka     │  dvojitá přesnost (double)    ║
╟────────────┼───────────────────────────┼───────────────────────────────╢
║ complex64  │ komplexní číslo (dvojice) │  2×float32                    ║
║ complex128 │ komplexní číslo (dvojice) │  2×float64                    ║
╚════════════╧═══════════════════════════╧═══════════════════════════════╝

4. Od typu float k typu Decimal

Datový typ Decimal, kterému se budeme dnes věnovat, je postaven na podobném principu, jako v úterním článku popsané typy Decimal32, Decimal64 či Decimal128. Jedná se tedy o vhodným způsobem uložené hodnoty s plovoucí řádovou čárkou, přičemž základem exponentu je hodnota 10 a nikoli dnes obvyklejší hodnota 2. Navíc není typ Decimal omezen pouze na podporu HW (tedy na FPU jednotku), takže umožňuje specifikovat jak způsoby zaokrouhlení, tak i přesnost (počet cifer), chování při dělení nulou atd. To jsou v praxi poměrně užitečné vlastnosti a ještě se k nim vrátíme v dalším textu.

Připomeňme si nejdříve, s jakými problémy se setkáme při použití datového typu (formátu) float, což je interně ve skutečnosti typ double:

x = 0.1
y = 0.1
z = 0.1
 
print(x+y+z-0.3)

V ideálním světě by měl tento skript vypsat nulu, ovšem ve skutečnosti dostaneme:

5.551115123125783e-17

To je sice hodnota, která je z určitého pohledu blízká nule, ale o nulu se nejedná.

Stejně tak i v Pythonu narazíme na problém při implementaci počítané programové smyčky s testem na koncovou hodnotu realizovaným porovnáním dvou hodnot typu float (ve skutečnosti se s takto zapsaným kódem často nesetkáme, což je ostatně jen dobře):

x = 0
 
while x != 1.0:
    x += 0.1
    print(x)

Tento skript neskončí po deseti iteracích, ale bude pokračovat dále:

.1
0.2
0.30000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999
1.0999999999999999
1.2
1.3
1.4000000000000001
1.5000000000000002
1.6000000000000003
1.7000000000000004
1.8000000000000005
1.9000000000000006
2.0000000000000004
...
...
...

Chyba se navíc bude akumulovat:

...
...
...
113638.50000212675
113638.60000212675
113638.70000212676
113638.80000212677
...
...
...

5. Konstruktory typu Decimal

Datový typ Decimal umožňuje přesnou reprezentaci „problematických“ hodnot 0,1, 0,2 atd. Pokusme se tedy první příklad (tedy ve skutečnosti výpočet výrazu 0,1+0,1+0,1–0,3) přepsat tak, aby se použily hodnoty typu Decimal a nikoli float. Hodnoty typu Decimal se vytváří konstruktorem nazvaným taktéž Decimal:

from decimal import Decimal
 
x = Decimal(0.1)
y = Decimal(0.1)
z = Decimal(0.1)
 
print(x+y+z-Decimal(0.3))

Pokud ovšem očekáváme, že se vypíše nula, budeme překvapeni:

2.775557561565156540423631668E-17

Proč tomu tak je? Hodnoty 0,1 atd. samozřejmě lze typem Decimal reprezentovat zcela přesně, ovšem je nutné je nějakým způsobem získat v přesné podobě. A zápis Decimal(0.1) znamená „zkonstruuj hodnotu Decimal z float hodnoty 0,1“. Jenže již víme, že typ float nedokáže 0,1 reprezentovat přesně, takže sice provedeme přesné uložení i přesný výpočet, ovšem s nepřesnými vstupními daty (což je ovšem obvyklé i v jiných oblastech, než jenom v IT). Příklad si tedy upravme do takové podoby, že zkonstruujeme dekadický formát s plovoucí řádovou čárkou ze zápisu numerické hodnoty reprezentované řetězcem (i to je totiž možné a někdy se tomuto postupu nevyhneme):

from decimal import Decimal
 
x = Decimal("0.1")
y = Decimal("0.1")
z = Decimal("0.1")
 
print(x+y+z-Decimal("0.3"))

Nyní se po spuštění vypíše „přesná nula“, tedy skript se chová podle očekávání:

0.0
Poznámka: zde jsme si mj. ukázali i přetížení základních aritmetických operátorů pro datový typ Decimal, což je pochopitelně velmi užitečné a oprostí nás to od nutnosti psát výrazy pomocí metod.

6. Přepis počítané programové smyčky

Podobným způsobem můžeme postupovat i při realizaci počítané programové smyčky. První (nekorektní) varianta bude vypadat následovně:

from decimal import Decimal
 
x = Decimal(0)
step = Decimal(0.1)
 
while x != Decimal("1.0"):
    x += step
    print(x)

Jedná se o (prakticky) nekonečnou smyčku, která zcela jistě u hodnoty 1.0 neskončí:

...
...
...
52110.30000000000289270478638
52110.40000000000289271033750
52110.50000000000289271588862
52110.60000000000289272143974
52110.70000000000289272699086
52110.80000000000289273254198
52110.90000000000289273809310
...
...
...

Korektní zápis, v němž se vyhneme hodnotám typu float, by měl vypadat takto:

from decimal import Decimal
 
x = Decimal(0)
step = Decimal("0.1")
 
while x != Decimal("1.0"):
    x += step
    print(x)

Nyní bude vše funkční přesně podle předpokladů:

0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1.0

7. Výpočet hodnoty π

Zkusme si nyní provést nějaký výpočet, v němž by se do jisté míry využila konfigurovatelná přesnost hodnot uložených v typu Decimal. Pro jednoduchost jsem vybral jeden z dnes již nepoužívaných výpočtů hodnoty π z nekonečné řady (nepoužívaný proto, že konverguje velmi pomalu, což ovšem pro nás bude výhoda). Jedná se o takzvaný Wallis product, což je forma řady, která vypadá následovně:

První realizace tohoto výpočtu bude založena na standardním numerickém datovém typu float, tedy na hodnotách s plovoucí čárkou a exponentem, jehož báze je rovna 2:

from math import pi
 
result = 2
 
for n in range(1, 1000):
    m = 4 * n * n
    u = m / (m-1)
    result *= u
 
    abs_error = pi - result
    rel_error = 100.0 * abs_error / pi
    print(result, "\t", abs_error, "\t", rel_error)

Po spuštění tohoto skriptu se bude vypisovat jak postupně se zpřesňující výsledek počítané nekonečně řady, tak i absolutní a relativní chyba vypočtená vůči konstantě π (ovšem typu float). Prvních 100 výsledků vypadá takto:

2.6666666666666665       0.4749259869231266      15.11736368432249
2.844444444444444        0.297148209145349       9.458521263277328
2.9257142857142853       0.21587836787550785     6.87162187079954
2.972154195011337        0.16943845857845607     5.393393646526529
3.0021759545569062       0.1394166990328869      4.437771360127803
3.0231701920013605       0.11842246158843261     3.769504026981831
3.038673628883419        0.10291902470637426     3.276014304043273
3.0505899960555105       0.0910026575342826      2.896704556215998
3.06003454712689         0.08155810646290318     2.5960751585572193
3.0677038066434985       0.0738888469462946      2.3519550461726566
3.074055160280442        0.06753749330935133     2.1497851808438146
3.0794013431678864       0.06219131042190673     1.9796108942017927
3.083963419231839        0.057629234357954306    1.8343955029339434
3.087902069831113        0.05369058375868008     1.7090243605366735
3.09133688859622         0.050255764993573315    1.5996906835183655
3.0943587232869296       0.04723393030286349     1.503502697871758
3.09703782174865         0.04455483184114328     1.4182243452292251
3.0994293567461395       0.04216329684365361     1.3420994219436762
3.101577263438271        0.04001539015152211     1.2737294284730982
3.103516961539233        0.03807569205056005     1.2119869202982834
3.105277322833356        0.036315330756437       1.1559528799808176
3.1068821173154406       0.0347105362743525      1.1048706850867482
3.108351092311807        0.033241561277986165    1.058111758696728
3.1097007888347385       0.031891864755054566    1.0151495840370264
3.110945166901499        0.0306474866882942      0.9755397999570167
3.1120960900117107       0.02949656357808239     0.9389047795352351
3.1131637044508227       0.028428949138970427    0.9049215564750451
3.1141567391252885       0.027435914464504663    0.8733122810544696
3.115082744697434        0.026509908892359046    0.8438366082269468
3.1159482858879586       0.025644367701834536    0.8162855764426229
3.1167590973076535       0.02483355628213957     0.7904766473706606
3.1175202106403295       0.024072442949463646    0.7662496575409568
3.1182360591387543       0.02335659445103877     0.7434634921351108
3.1189105640185164       0.02268208957127671     0.7219933349843635
3.119547206305518        0.022045447284275266    0.7017283815928417
3.12014908691642         0.021443566673373216    0.6825699267175955
3.120718977160606        0.02087367642918725     0.6644297568411868
3.1212593613990753       0.020333292190717778    0.6472287922969135
3.121772473245434        0.019820180344359173    0.6308959349555173
3.1222603264214372       0.019332327168355867    0.6153670860627161
3.1227247411658103       0.01886791242398278     0.6005843056203689
3.1231673669264293       0.01842528666336385     0.5864950900719064
3.1235897019320986       0.0180029516576945      0.5730517493133022
3.123993110133304        0.017599543456489286    0.5602108674521783
3.1243788359155156       0.017213817674277543    0.5479328344687809
3.124748016919405        0.01684463667038827     0.5361814381358597
3.125101695246164        0.016490958343629014    0.5249235073422185
3.125440827280374        0.016151826309418915    0.5141285994211491
3.1257662923253897       0.0158263612644034      0.50376872527758
3.126078900215411        0.015513753374382322    0.49381810708830354
3.1263793980429813       0.015213255546811855    0.4842529641590604
3.1266684761195456       0.014924177470247546    0.47505132319411897
3.126946773269178        0.014645880320615312    0.466192849791648
3.127214881540915        0.014377772048878246    0.4576586984454921
3.1274733504128496       0.014119303176943543    0.4494313787247333
3.1277226905508084       0.01386996303898469     0.44149463563126
3.1279633771757065       0.013629276414086622    0.43383334241353355
3.1281958530863103       0.013396800503482797    0.4264334043490559
3.1284205313778486       0.013172122211944526    0.4192816722083043
3.1286377978915914       0.012954855698201762    0.4123658642822035
3.1288480134259524       0.012744640163840693    0.40567449600054983
3.129051515735769        0.012541137854024331    0.3991968162929714
3.1292486213430593       0.01234403224673386     0.3929227499506897
3.129439627179679        0.012153026410114176    0.38684284533920454
3.1296248120798023       0.011967841509990862    0.3809482268910837
3.129804438138006        0.01178821545178721     0.37523055187683896
3.129978751946869        0.011613901642923974    0.36968197101088696
3.130147985726374        0.011444667863419244    0.36429509250162667
3.130312358355987        0.011280295233806026    0.35906294919923526
3.1304720763190645       0.011120577270728571    0.3539789685343661
3.13062733456815         0.010965319021642994    0.34903694497481363
3.1307783173187924       0.010814336271000702    0.34423101475754725
3.1309251987786713       0.010667454811121857    0.33955563267989286
3.131068143818108        0.010524509771685064    0.33500555075652655
3.131207308587378        0.010385345002414947    0.3305757985698101
3.1313428410856936       0.01024981250409951     0.32626166515851096
3.1314748816862035       0.010117771903589645    0.3220586823065175
3.131603563620935        0.009989089968858167    0.31796260910668883
3.131729013429196        0.009863640160597154    0.31396941768775466
3.131851351372609        0.009741302217184167    0.31007528000337997
3.1319706918196375       0.009621961770155618    0.306276555592302
3.1320871436021926       0.009505509987600469    0.3025697802271991
3.1322008103466525       0.009391843243140574    0.298951655378008
3.132311790781417        0.009280862808376256    0.2954190384221622
3.1324201790229056       0.009172474566887523    0.2919689335409682
3.132526064841755        0.009066588748038118    0.2885984832463251
3.132629533910784        0.00896311967900898     0.28530496048770426
3.132730668036172        0.008861985553620944    0.28208576129354795
3.1328295453731676       0.008763108216625515    0.27893839790503083
3.132926240627508        0.008666412962285097    0.27586049236466975
3.133020825243655        0.008571828346138233    0.27284977052462517
3.133113367580835        0.008479286008958198    0.26990405644312926
3.133203933077802        0.008388720511991021    0.2670212671399492
3.1332925844071484       0.008300069182644698    0.26419940768452227
3.133379381619936        0.008213271969857328    0.2614365665921804
3.133464382281348        0.008128271308445179    0.25873091150621563
3.1335476415980024       0.008045011991790751    0.25608068514541454
3.1336292125375205       0.007963441052272646    0.25348420149802325
3.1337091459408963       0.007883507648896781    0.25093984224493776

Přepis do podoby využívající typ Decimal může vypadat následovně (nejedná se o nejideálnější způsob, protože neustále vytváříme nové objekty):

from decimal import Decimal
from math import pi
 
result = Decimal(2)
 
for n in range(1, 1000):
    m = Decimal(4 * n * n)
    u = m / (m-1)
    result *= u
 
    abs_error = Decimal(pi) - result
    rel_error = Decimal(100.0) * abs_error / Decimal(pi)
    print(result, "\t", abs_error, "\t", rel_error)

Opět si vypišme prvních 100 výsledků:

2.666666666666666666666666666    0.4749259869231264493312968025          15.11736368432248428105764000
2.844444444444444444444444445    0.2971482091453486715535190235          9.458521263277316566461482623
2.925714285714285714285714288    0.2158783678755074017122491805          6.871621870799525611217524929
2.972154195011337868480725626    0.1694384585784552475172378425          5.393393646526502208220977703
3.002175954556906937859318814    0.1394166990328861781386446545          4.437771360127780008304017886
3.023170192001360832529663701    0.1184224615884322834682997675          3.769504026981820427942507516
3.038673628883419093209303002    0.1029190247063740227886604665          3.276014304043265660906315248
3.050589996055510932790515956    0.09100265753428218320744751254         2.896704556215984349772614491
3.060034547126890223604108884    0.08155810646290289239385458454         2.596075158557210307511848612
3.067703806643498971031688105    0.07388884694629414496627536354         2.351955046172641912292580039
3.074055160280442033083513547    0.06753749330935108291444992154         2.149785180843806802380142291
3.079401343167886280097571832    0.06219131042190683590039163654         1.979610894201796031601672956
3.083963419231838704216234900    0.05762923435795441178172856854         1.834395502933946840537379182
3.087902069831113083148822684    0.05369058375868003284914078454         1.709024360536672187715587841
3.091336888596219994253548848    0.05025576499357312174441462054         1.599690683518359253552868850
3.094358723286929886721049874    0.04723393030286322927691359454         1.503502697871749634054875541
3.097037821748650172337258576    0.04455483184114294366070489254         1.418224345229214352352758525
3.099429356746139477489642560    0.04216329684365363850832090854         1.342099421943677066138359093
3.101577263438271244279309673    0.04001539015152187171865379554         1.273729428473090563758690563
3.103516961539233265069978411    0.03807569205055985092798505754         1.211986920298276986875487695
3.105277322833356483030880270    0.03631533075643663296708319854         1.155952879980805788342802227
3.106882117315440904985934989    0.03471053627435221101202847954         1.104870685086739021308354047
3.108351092311807543711696659    0.03324156127798557228626680954         1.058111758696709110680131090
3.109700788834739288194419932    0.03189186475505382780354353654         1.015149584037002948765532783
3.110945166901499888149679804    0.03064748668829322784828366454         0.9755397999569857430627578833
3.112096090011711319850808062    0.02949656357808179614715540654         0.9389047795352162224349601430
3.113163704450823399205816916    0.02842894913896971679214655254         0.9049215564750224715678709208
3.114156739125289371581959122    0.02743591446450374441600434654         0.8733122810544403415747506356
3.115082744697434863515227620    0.02650990889235825248273584854         0.8438366082269215905612432796
3.115948285887959296653186839    0.02564436770183381934477662954         0.8162855764426000905864061564
3.116759097307654315986169714    0.02483355628213880001179375454         0.7904766473706361561837484279
3.117520210640330177845995396    0.02407244294946293815196807254         0.7662496575409342358311681616
3.118236059138755052743319391    0.02335659445103806325464407754         0.7434634921350882965052969970
3.118910564018516842717955626    0.02268208957127627328000784254         0.7219933349843496177894210411
3.119547206305517968834044207    0.02204544728427514716391926154         0.7017283815928379520653527282
3.120149086916420056036211687    0.02144356667337305996175178154         0.6825699267175905737037986486
3.120718977160605703535031086    0.02087367642918741246293238254         0.6644297568411919601099546061
3.121259361399075072488024164    0.02033329219071804350993930454         0.6472287922969220366398438068
3.121772473245433625023366596    0.01982018034435949097459687254         0.6308959349555274816565526958
3.122260326421436974550640135    0.01933232716835614144732333354         0.6153670860627247824038032430
3.122724741165810236037260785    0.01886791242398287996070268354         0.6005843056203720715280638599
3.123167366926429061017563728    0.01842528666336405498039974054         0.5864950900719128755070189269
3.123589701932098625461244263    0.01800295165769449053671920554         0.5730517493133019103786223420
3.123993110133303855814526099    0.01759954345648926018343736954         0.5602108674521774498220393954
3.124378835915515647869818670    0.01721381767427746812814479854         0.5479328344687785335916185823
3.124748016919404991559747750    0.01684463667038812443821571854         0.5361814381358550760155335160
3.125101695246164403556528706    0.01649095834362871244143476254         0.5249235073422088796460955512
3.125440827280374513638303695    0.01615182630941860235965977354         0.5141285994211391247768222204
3.125766292325389652086042768    0.01582636126440346391192070054         0.5037687252775820216970321930
3.126078900215411193205363304    0.01551375337438192279260016454         0.4938181070882908507821104145
3.126379398042981645112813593    0.01521325554681147088514987554         0.4842529641590481602938649400
3.126668476119545952245972429    0.01492417747024716375199103954         0.4750513231941067870308315545
3.126946773269178310586181240    0.01464588032061480541178222854         0.4661928497916318521654136838
3.127214881540915357513265712    0.01437777204887775848469775654         0.4576586984454766289683087291
3.127473350412850303819366486    0.01411930317694281217859698254         0.4494313787247100760820345200
3.127722690550808754772393621    0.01386996303898436122556984754         0.4414946356312495570735104366
3.127963377175706854715046362    0.01362927641408626128291710654         0.4338333424135220656966019552
3.128195853086310771984070150    0.01339680050348234401389331854         0.4264334043490414653298755285
3.128420531377848968548889805    0.01317212221194414744907366354         0.4192816722082922763235787755
3.128637797891591440176679853    0.01295485569820167582128361554         0.4123658642822007624876403921
3.128848013425952227077182217    0.01274464016384088892078125154         0.4056744960005560806870953089
3.129051515735768549173252276    0.01254113785402456682471119254         0.3991968162929788810825872044
3.129248621343058991286586024    0.01234403224673412471137744454         0.3929227499506981238467498927
3.129439627179678844731699043    0.01215302641011427126626442554         0.3868428453392075969666819329
3.129624812079801909933470255    0.01196784150999120606449321354         0.3809482268910946439870361717
3.129804438138005422641381263    0.01178821545178769335658220554         0.3752305518768543348923903778
3.129978751946868580838130992    0.01161390164292453515983247654         0.3696819710109048419564333907
3.130147985726373683221523159    0.01144466786341943277644030954         0.3642950925016326551406429883
3.130312358355986999068985300    0.01128029523380611692897816854         0.3590629491992381601900122975
3.130472076319064502359922031    0.01112057727072861363804143754         0.3539789685343674646524945585
3.130627334568150405474654955    0.01096531902164271052330851354         0.3490369449748046202079501808
3.130778317318792708363754287    0.01081433627100040763420918154         0.3442310147575379119668220467
3.130925198778671610203227134    0.01066745481112150579473633454         0.3395556326798816857370292939
3.131068143818108156411974941    0.01052450977168495958598852754         0.3350055507565232362865310681
3.131207308587378706576711683    0.01038534500241440942125178554         0.3305757985697930048645250706
3.131342841085694396257990163    0.01024981250409871973997330554         0.3262616651584858063623766116
3.131474881686204018623423770    0.01011777190358909737453969854         0.3220586823065000794303235811
3.131603563620935319384410966    0.009989089968857796613552502544        0.3179626091066770467645923551
3.131729013429196383171591371    0.009863640160596732826372097544        0.3139694176877412889248600854
3.131851351372609375725330641    0.009741302217183740272632827544        0.3100752800033664204256579858
3.131970691819638016100886993    0.009621961770155099897076475544        0.3062765555922854985196421029
3.132087143602193124411580463    0.009505509987599991586383005544        0.3025697802271838917339391842
3.132200810346653374570332472    0.009391843243139741427630996544        0.2989516553779814669069289524
3.132311790781417455404211590    0.009280862808375660593751878544        0.2954190384221432491932523927
3.132420179022906137277473786    0.009172474566886978720489682544        0.2919689335409508945529255147
3.132526064841755574661690311    0.009066588748037541336273157544        0.2885984832463067053528630935
3.132629533910784204077864109    0.008963119679008911920099359544        0.2853049604877021242366071549
3.132730668036172768539658392    0.008861985553620347458305076544        0.2820857612935290072753233763
3.132829545373168513032558044    0.008763108216624602965405424544        0.2789383979050018327340007369
3.132926240627508868244540900    0.008666412962284247753422568544        0.2758604923646427167684689059
3.133020825243655579257077340    0.008571828346137536740886128544        0.2728497705246030054716893178
3.133113367580836015103459178    0.008479286008957100894504290544        0.2699040564430943539580420030
3.133203933077803231059958774    0.008388720511989884938004694544        0.2670212671399130588100135593
3.133292584407149291191556542    0.008300069182643824806406926544        0.2641994076844944444608867570
3.133379381619936547051585671    0.008213271969856568946377797544        0.2614365665921562770447384371
3.133464382281348258972673256    0.008128271308444857025290212544        0.2587309115062053820084430413
3.133547641598002473088761277    0.008045011991790642909202191544        0.2560806851454110736620104263
3.133629212537520838375058004    0.007963441052272277622905464544        0.2534842014980115008668434813
3.133709145940896537195004822    0.007883507648896578802958646544        0.2509398422449313287244274752

8. Problém zastavení výpočtů

Zkusme si nyní předchozí výpočet nepatrně upravit do takové podoby, že výpočet přibližné hodnoty π ukončíme ve chvíli, kdy je absolutní chyba menší než zadaná hodnota ε. Mohlo by se zdát, že zde nemůže dojít k žádným podstatným problémům, takže si to otestujme; nejprve ve variantě založené na typu float:

from math import pi
 
result = 2
 
n = 1
while True:
    m = 4 * n * n
    u = m / (m-1)
    result *= u
 
    abs_error = pi - result
 
    n += 1
 
    if abs(abs_error) < 0.00000001:
        rel_error = 100.0 * abs_error / pi
        print(result, "\t", abs_error, "\t", rel_error)
        break

Tento výpočet ovšem nemusí nikdy skončit, což znamená, že vypočtená absolutní chyba nikdy nebude menší než nastavená hodnota ε=0.00000001. U typu float si tedy musíme dát pozor i na to, o kolik řádů se liší hodnoty, s nimiž provádíme výpočty (zde konkrétně není problém ve výpočtu chyby, ale podílu m/m-1. Konkrétně se výpočet „zasekne“ na absolutní chybě:

1.0523530935557801e-08

Pokusme se tedy výpočet upravit do podoby využívající výpočty s hodnotami Decimal:

from decimal import Decimal
from math import pi
 
result = Decimal(2)
 
n = Decimal(1)
 
while True:
    m = Decimal(4 * n * n)
    u = m / (m-1)
    result *= u
 
    abs_error = Decimal(pi) - result
    n += 1
 
    if abs(abs_error) < 0.00000001:
        rel_error = Decimal(100.0) * abs_error / Decimal(pi)
        print(result, "\t", abs_error, "\t", rel_error)
        break

Tento výpočet relativně rychle (v řádu minut) skončí s následujícími výsledky:

vypočtená hodnota                absolutní chyba                         relativní chyba (v %)
3.141592643589793147458504120    9.999999968539459348544185162E-9        3.183098851823705725414918062E-7

9. Výpočet harmonické řady

Vyzkoušejme si nyní další vlastnosti formátů float a Decimal na jednoduchém testu – výpočtu součtu harmonické řady. Ta je divergentní, což bylo ostatně dokázáno již ve čtrnáctém století. Ovšem při naivním výpočtu této řady se ukazují některé nepříjemné vlastnosti hodnot s plovoucí řádovou čárkou. Ve výpočtu zjišťujeme, kdy je již další přičítaný člen z důvodu velkého dynamického rozsahu (mezisoučet versus hodnota n-tého prvku) považován za tak malou hodnotu (relativně k prvnímu operandu) že již může být výpočet ukončen:

n = 1
h1 = 0.0
h2 = 0.0
 
while True:
        h2 = h1 + 1.0 / n;
 
        if n % 1000000 == 0:
            print(n, h1, h2, h2 - h1)
 
        if h1 == h2:
            break
        h1 = h2
        n += 1
 
print(h1, h2, n)

Tento výpočet skutečně skončí, ovšem po provedení velkého množství iterací.

Úprava pro typ Decimal je (zdánlivě) triviální, ovšem navíc ještě bude možné měnit přesnost výpočtů, což si ukážeme v navazujících kapitolách:

from decimal import Decimal
 
n = Decimal(1)
h1 = Decimal(0.0)
h2 = Decimal(0.0)
one = Decimal(1.0)
 
while True:
        h2 = h1 + one / n;
 
        if n % 1000000 == 0:
            print(n, h1, h2, h2 - h1)
 
        if h1 == h2:
            break
        h1 = h2
        n += 1
 
print(h1, h2, n)
Poznámka: ve skutečnosti se i tento výpočet po několika dnech zastaví, protože přesnost typu Decimal není nekonečná.

10. Hodnoty, které nelze uložit přesně

I když je typ Decimal vhodný pro uložení často používaných hodnot s desetinnou řádovou čárkou (tečkou), pochopitelně nedokáže reprezentovat či přesně uložit všechny hodnoty. Je tomu tak z toho důvodu, že množina reprezentovatelná typem Decimal je spočetná, zatímco množina reálných čísel je nespočetná. Jednotlivé reprezentace, resp. formáty se od sebe liší především tím, kterou podmnožinu z nespočetné množiny reálných čísel mohou reprezentovat přesně.

Příkladem hodnot, které nelze uložit přesně, jsou některé zlomky, například známý 1/3 či 1/7. Podíl 1/3, který bude znovu vynásoben hodnotou 3, již nedá kýžený výsledek 1,0:

from decimal import Decimal
 
x = Decimal(1)
y = Decimal(3)
z = x/y
 
print(z)
 
print(z*Decimal(3))

Výsledek nebude příznivý:

0.3333333333333333333333333333
0.9999999999999999999999999999

Dalším příkladem jsou iracionální čísla, například odmocnina ze dvou. Pokusme se zobrazit hodnotu odmocniny a současně i výsledek zpětného výpočtu čtverce (což by měla být přesná dvojka):

from decimal import Decimal, getcontext
 
x = Decimal(2)
 
for precision in range(1, 30):
    getcontext().prec = precision
    z = x.sqrt()
    y = x.sqrt()
    print(f"{z:<30}   {y*y:<30}")
 

V závislosti na poslední cifře je čtverec vypočítán přesně nebo nepřesně:

1                                1
1.4                              2.0
1.41                             1.99
1.414                            1.999
1.4142                           2.0000
1.41421                          1.99999
1.414214                         2.000001
1.4142136                        2.0000001
1.41421356                       1.99999999
1.414213562                      1.999999999
1.4142135624                     2.0000000001
1.41421356237                    1.99999999999
1.414213562373                   2.000000000000
1.4142135623731                  2.0000000000000
1.41421356237310                 2.00000000000001
1.414213562373095                2.000000000000000
1.4142135623730950               1.9999999999999999
1.41421356237309505              2.00000000000000000
1.414213562373095049             2.000000000000000001
1.4142135623730950488            2.0000000000000000000
1.41421356237309504880           2.00000000000000000000
1.414213562373095048802          2.000000000000000000001
1.4142135623730950488017         2.0000000000000000000000
1.41421356237309504880169        2.00000000000000000000000
1.414213562373095048801689       2.000000000000000000000001
1.4142135623730950488016887      1.9999999999999999999999999
1.41421356237309504880168872     1.99999999999999999999999999
1.414213562373095048801688724    1.999999999999999999999999999
1.4142135623730950488016887242   2.0000000000000000000000000000

11. Dělení nulou

Hodnotu typu Decimal lze podělit nulou. V závislosti na takzvaném kontextu buď tato operace vede k vyhození výjimky, nebo se vypočte kladné, popř. záporné nekonečno. Ukažme si tedy všechny případy, které mohou nastat.

Dělení nulou s vyhozením výjimky:

from decimal import Decimal
 
x = Decimal(1)
y = Decimal(0)
z = x/y
 
print(z)

Výsledek:

Traceback (most recent call last):
  File "div_by_zero_A.py", line 5, in <module>
    z = x/y
decimal.DivisionByZero: [<class 'decimal.DivisionByZero'>]

Dělení kladnou nulou bez vyhození výjimky. Nutno nastavit kontext:

from decimal import Decimal, ExtendedContext, setcontext
 
setcontext(ExtendedContext)
 
x = Decimal(1)
y = Decimal(0)
z = x/y
 
print(z)

Výsledek:

Infinity

Dělení zápornou nulou bez vyhození výjimky:

from decimal import Decimal, ExtendedContext, setcontext
 
setcontext(ExtendedContext)
 
x = Decimal(1)
y = Decimal("-0")
z = x/y
 
print(z)

Výsledek:

-Infinity

12. Operace 0/0 a 00

Výsledkem dělení nulou je většinou kladné či záporné nekonečno, ovšem v některých specifických případech není výsledek definován. V těchto případech se vrací hodnota NaN (Not a Number), která je opět typu Decimal. Ukažme si to na jednoduchém příkladu:

from decimal import Decimal, ExtendedContext, setcontext
 
setcontext(ExtendedContext)
 
x = Decimal(0)
y = Decimal(0)
z = x/y
 
print(z)

Tento skript po svém spuštění vypíše:

NaN

Podobně je možné vypočítat hodnotu xy, a to konkrétně s využitím přetíženého operátoru **:

from decimal import Decimal, ExtendedContext, setcontext
 
setcontext(ExtendedContext)
 
x = Decimal(2)
y = Decimal(100)
z = x**y
 
print(z)

S výsledkem:

1.26765060E+30

Ovšem operace 00 není dobře definována (má se vrátit nula nebo jednička?) a proto i výsledkem této operace ne hodnota NaN:

from decimal import Decimal, ExtendedContext, setcontext
 
setcontext(ExtendedContext)
 
x = Decimal(0)
y = Decimal(0)
z = x**y
 
print(z)

Zkusme si tento skript spustit:

NaN

13. Operace s nekonečny

Datový typ Decimal podporuje i práci s hodnotami kladné nekonečno a záporné nekonečno. Je však zapotřebí si uvědomit, že tyto hodnoty vlastně neleží na běžné číselné ose (používá se zde pojem rozšířená reálná čísla) a taktéž nemůžeme s nekonečny pracovat stejně, jako s jinými číselnými hodnotami. Typicky totiž zobecňujeme operace s běžnými čísly tak, že namísto konkrétních čísel použijeme obecnější symboly (například x), pro které platí nějaká pravidla, například x-x=0, x/x=1 atd. Při práci s nekonečny tyto vztahy většinou neplatí, což ostatně uvidíme i v další čtveřici demonstračních příkladů.

Pokusme se například k nekonečnu přičíst nějakou hodnotu:

from decimal import Decimal, ExtendedContext, setcontext
 
setcontext(ExtendedContext)
 
x = Decimal("inf")
y = Decimal(1)
z = x+y
 
print(z)

Výsledkem bude v tomto případě opět nekonečno:

Infinity
Poznámka: podobně můžeme nekonečno vynásobit nějakou hodnotou s podobným výsledkem (maximálně se změní znaménko).

Dvě nekonečna můžeme i sečíst:

from decimal import Decimal, ExtendedContext, setcontext
 
setcontext(ExtendedContext)
 
x = Decimal("inf")
y = Decimal("inf")
z = x+y
 
print(z)

Výsledkem bude opět, jak již zajisté očekáváte, nekonečno (tedy jakoby platilo x+x=x):

Infinity

Některé operace s nekonečny ovšem vrací NaN (tedy „nečíslo“). Například rozdíl dvou nekonečen není nula, ale právě NaN:

from decimal import Decimal, ExtendedContext, setcontext
 
setcontext(ExtendedContext)
 
x = Decimal("inf")
y = Decimal("inf")
z = x-y
 
print(z)

Výsledek:

NaN

Podobně podíl dvou nekonečen není 1, ale opět (ne)hodnota NaN:

from decimal import Decimal, ExtendedContext, setcontext
 
setcontext(ExtendedContext)
 
x = Decimal("inf")
y = Decimal("inf")
z = x/y
 
print(z)

Výsledek:

NaN

Vynásobení nekonečna nulou vrací (nepřekvapivě) opět nehodnotu NaN

from decimal import Decimal, ExtendedContext, setcontext
 
setcontext(ExtendedContext)
 
x = Decimal("inf")
y = Decimal("0")
z = x*y
 
print(z)

Výsledkem je:

NaN

14. Nastavení přesnosti výpočtů i uložení výsledků

U dekadických formátů Decimal32, Decimal64 a Decimal128, které jsme si popsali minule, byla přesnost přímo určena způsobem uložení čísla do 32bitového, 64bitového či 128bitového slova. Ovšem v případě typu Decimal v Pythonu je tomu jinak – zde si totiž můžeme přesnost řídit podle potřeb (a zaplatit za to spotřebou paměti a pomalými výpočty). Například je možné nastavit přesnost (tedy vlastně počet uložených cifer) na 100 a vypočítat přibližnou hodnotu 1/3:

from decimal import Decimal, getcontext
 
getcontext().prec = 100
 
x = Decimal(1)
y = Decimal(3)
z = x/y
 
print(z*Decimal(3))

Výsledek vypsaný na obrazovku by měl vypadat následovně:

0.9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999

Přesnost lze řídit, a to i za běhu programu:

from decimal import Decimal, getcontext
 
x = Decimal(1)
y = Decimal(3)
 
for precision in range(1, 30):
    getcontext().prec = precision
    z = x/y
    print(z*Decimal(3))

Výsledky:

0.9
0.99
0.999
0.9999
0.99999
0.999999
0.9999999
0.99999999
0.999999999
0.9999999999
0.99999999999
0.999999999999
0.9999999999999
0.99999999999999
0.999999999999999
0.9999999999999999
0.99999999999999999
0.999999999999999999
0.9999999999999999999
0.99999999999999999999
0.999999999999999999999
0.9999999999999999999999
0.99999999999999999999999
0.999999999999999999999999
0.9999999999999999999999999
0.99999999999999999999999999
0.999999999999999999999999999
0.9999999999999999999999999999
0.99999999999999999999999999999
Poznámka: již uložené hodnoty se nemění; změna přesnosti se tak týká pouze nových operací a taktéž nově vytvořených numerických hodnot.

15. Změna zaokrouhlovacího režimu

Popsat si musíme ještě jednu důležitou vlastnost datového typu Decimal. Jedná se o možnost výběru zaokrouhlovacího režimu při provádění operací, které vedou ke vzniku hodnoty, která se nevejde do stanoveného počtu cifer. K dispozici jsou následující zaokrouhlovací režimy, jež je možné zvolit pro kontext:

ROUND_CEILING,
ROUND_DOWN,
ROUND_FLOOR,
ROUND_HALF_DOWN,
ROUND_HALF_EVEN,
ROUND_HALF_UP,
ROUND_UP,
ROUND_05UP,

Podívejme se na funkci jednotlivých režimů při výpočtu hodnoty 1/7 (která je vždy nepřesná) pro různé zaokrouhlovací režimy a různou uživatelem zadanou přesnost. Ve skriptu budeme postupně jednotlivými režimy procházet a vypisovat hodnotu 1/7 (resp. přesněji řečeno výsledek operace 1/7) pro různé přesnosti:

from decimal import Decimal, getcontext
from decimal import (
    ROUND_CEILING,
    ROUND_DOWN,
    ROUND_FLOOR,
    ROUND_HALF_DOWN,
    ROUND_HALF_EVEN,
    ROUND_HALF_UP,
    ROUND_UP,
    ROUND_05UP,
)
 
x = Decimal(1)
y = Decimal(7)
 
 
roundings = (
    ROUND_CEILING,
    ROUND_DOWN,
    ROUND_FLOOR,
    ROUND_HALF_DOWN,
    ROUND_HALF_EVEN,
    ROUND_HALF_UP,
    ROUND_UP,
    ROUND_05UP,
)
 
for round_mode in roundings:
    getcontext().rounding = round_mode
    print(round_mode)
    for i in range(1, 10):
        z = (x / y).quantize(Decimal(10) ** -i)
        print(z)
    print()

Tento skript po svém spuštění vypíše:

ROUND_CEILING
0.2
0.15
0.143
0.1429
0.14286
0.142858
0.1428572
0.14285715
0.142857143
 
ROUND_DOWN
0.1
0.14
0.142
0.1428
0.14285
0.142857
0.1428571
0.14285714
0.142857142
 
ROUND_FLOOR
0.1
0.14
0.142
0.1428
0.14285
0.142857
0.1428571
0.14285714
0.142857142
 
ROUND_HALF_DOWN
0.1
0.14
0.143
0.1429
0.14286
0.142857
0.1428571
0.14285714
0.142857143
 
ROUND_HALF_EVEN
0.1
0.14
0.143
0.1429
0.14286
0.142857
0.1428571
0.14285714
0.142857143
 
ROUND_HALF_UP
0.1
0.14
0.143
0.1429
0.14286
0.142857
0.1428571
0.14285714
0.142857143
 
ROUND_UP
0.2
0.15
0.143
0.1429
0.14286
0.142858
0.1428572
0.14285715
0.142857143
 
ROUND_05UP
0.1
0.14
0.142
0.1428
0.14286
0.142857
0.1428571
0.14285714
0.142857142

Pro porovnání je pravděpodobně lepší si výsledky zobrazit vedle sebe a porovnávat tak jednotlivé řádky:

ROUND_CEILING  ROUND_DOWN   ROUND_FLOOR  ROUND_HALF_DOWN ROUND_HALF_EVEN ROUND_HALF_UP ROUND_UP    ROUND_05UP
0.2            0.1          0.1          0.1             0.1             0.1           0.2         0.1
0.15           0.14         0.14         0.14            0.14            0.14          0.15        0.14
0.143          0.142        0.142        0.143           0.143           0.143         0.143       0.142
0.1429         0.1428       0.1428       0.1429          0.1429          0.1429        0.1429      0.1428
0.14286        0.14285      0.14285      0.14286         0.14286         0.14286       0.14286     0.14286
0.142858       0.142857     0.142857     0.142857        0.142857        0.142857      0.142858    0.142857
0.1428572      0.1428571    0.1428571    0.1428571       0.1428571       0.1428571     0.1428572   0.1428571
0.14285715     0.14285714   0.14285714   0.14285714      0.14285714      0.14285714    0.14285715  0.14285714
0.142857143    0.142857142  0.142857142  0.142857143     0.142857143     0.142857143   0.142857143 0.142857142

16. Zachytávání výjimek při výpočtech

Poslední vlastností datového typu Decimal, kterou se dnes budeme zabývat, jsou takzvané „pasti“. Jedná se vlastně o určení toho, jak se má systém zachovat v případě, že dojde k dělení nulou, že výsledkem nějaké operace je nenormalizovaná hodnota, že došlo k zaokrouhlení výsledku (a tím pádem ke ztrátě přesnosti) atd. Systém (resp. právě aktuální kontext) buď bude tyto potenciální problémy ignorovat, nebo naopak vyhodí výjimku. Záleží tedy jen na programátorovi, které potenciální problémy bude chtít ignorovat a na které naopak bude chtít reagovat.

Nejprve se podívejme na to, jakým způsobem lze vypsat výchozí stav všech „pastí“. Je to jednoduché, protože i stav pastí je uložen v kontextech a jedná se o slovník, kde klíčem je třída s daným typem výjimky a hodnotou je pravdivostní hodnota True či False:

from decimal import Decimal, getcontext
 
c = getcontext()
 
for trap, state in c.traps.items():
    print(trap, state)

Povšimněte si, že ve výchozím nastavení dojde k vyhození výjimky při provedení neplatné operace, dále při dělení nulou (to jsme ostatně již viděli) a taktéž při přetečení výsledku:

<class 'decimal.InvalidOperation'> True
<class 'decimal.FloatOperation'> False
<class 'decimal.DivisionByZero'> True
<class 'decimal.Overflow'> True
<class 'decimal.Underflow'> False
<class 'decimal.Subnormal'> False
<class 'decimal.Inexact'> False
<class 'decimal.Rounded'> False
<class 'decimal.Clamped'> False

Jaké má povolená past důsledky již víme. Týká se to například dělení nulou:

from decimal import Decimal, getcontext
 
c = getcontext()
 
for trap, state in c.traps.items():
    print(trap, state)
 
x = Decimal(1)
y = Decimal(0)
z = x/y
 
print(z)

Dělení nulou v tomto případě vyhodí výjimku:

<class 'decimal.InvalidOperation'> True
<class 'decimal.FloatOperation'> False
<class 'decimal.DivisionByZero'> True
<class 'decimal.Overflow'> True
<class 'decimal.Underflow'> False
<class 'decimal.Subnormal'> False
<class 'decimal.Inexact'> False
<class 'decimal.Rounded'> False
<class 'decimal.Clamped'> False
Traceback (most recent call last):
  File "/home/ptisnovs/src/most-popular-python-libs/decimal/traps_B.py", line 10, in
    z = x/y
        ~^~
decimal.DivisionByZero: [<class 'decimal.DivisionByZero'>]

Tuto konkrétní past, tedy detekci dělení nulou s vyhozením výjimky, můžeme snadno zakázat:

from decimal import Decimal, getcontext, DivisionByZero
 
c = getcontext()
c.traps[DivisionByZero] = False
 
for trap, state in c.traps.items():
    print(trap, state)
 
x = Decimal(1)
y = Decimal(0)
z = x/y
 
print(z)

Tento skript již nezhavaruje a vypíše na konci hodnotu Infinity:

<class 'decimal.InvalidOperation'> True
<class 'decimal.FloatOperation'> False
<class 'decimal.DivisionByZero'> False
<class 'decimal.Overflow'> True
<class 'decimal.Underflow'> False
<class 'decimal.Subnormal'> False
<class 'decimal.Inexact'> False
<class 'decimal.Rounded'> False
<class 'decimal.Clamped'> False
 
Infinity

Naopak si můžeme nějakou past povolit. Například se to může týkat detekce operace, která vede k uložení nepřesné hodnoty. Příkladem může být výpočet podílu 1/7, jenž nebude uložen zcela přesně:

from decimal import Decimal, getcontext, Rounded
 
c = getcontext()
c.traps[Rounded] = True
 
for trap, state in c.traps.items():
    print(trap, state)
 
x = Decimal(1)
y = Decimal(2)
z = x/y
 
print(z)
 
x = Decimal(1)
y = Decimal(7)
z = x/y
 
print(z)

Povšimněte si, že při výpočtu druhého podílu došlo k vyhození výjimky typu decimal.Rounded:

<class 'decimal.InvalidOperation'> True
<class 'decimal.FloatOperation'> False
<class 'decimal.DivisionByZero'> True
<class 'decimal.Overflow'> True
<class 'decimal.Underflow'> False
<class 'decimal.Subnormal'> False
<class 'decimal.Inexact'> False
<class 'decimal.Rounded'> True
<class 'decimal.Clamped'> False
 
0.5
Traceback (most recent call last):
  File "/home/ptisnovs/src/most-popular-python-libs/decimal/traps_D.py", line 17, in
    z = x/y
        ~^~
decimal.Rounded: [<class 'decimal.Rounded'>]

17. Určení maximálního exponentu a reakce na přetečení hodnoty

Můžeme si i určit maximální (desítkový) exponent s vyhozením výjimky, pokud hodnota překročí tento exponent:

from decimal import Decimal, getcontext, Rounded
 
c = getcontext()
c.traps[Rounded] = True
c.Emax = 5
 
x = Decimal(1)
y = Decimal(2)
 
for i in range(20):
    x *= y
    print(x)

Výsledek:

2
4
8
16
32
64
128
256
512
1024
2048
4096
8192
16384
32768
65536
131072
262144
524288
Traceback (most recent call last):
  File "/home/ptisnovs/src/most-popular-python-libs/decimal/traps_E.py", line 11, in <module>
    x *= y
decimal.Overflow: [<class 'decimal.Overflow'>, <class 'decimal.Rounded'>]

V případě, že naopak past na přetečení zakážeme, bude výsledkem přetečení nekonečno:

from decimal import Decimal, getcontext, Overflow
 
c = getcontext()
c.traps[Overflow] = False
c.Emax = 5
 
x = Decimal(1)
y = Decimal(2)
 
for i in range(20):
    x *= y
    print(x)

Výsledek nyní bude vypadat odlišně:

2
4
8
16
32
64
128
256
512
1024
2048
4096
8192
16384
32768
65536
131072
262144
524288
Infinity

18. Příloha: „numerická věž“ v programovacích jazycích

S takzvanou „numerickou věží“ jsme se již na tomto serveru setkali, a to například při popisu možností mnoha existujících dialektů programovacího jazyka Scheme. Připomeňme si ve stručnosti, že se jedná o hierarchii datových typů reprezentujících různé typy (resp. přesněji řečeno množiny) čísel. Na vrcholu této hierarchie typicky stojí obecný typ number, pod ním leží komplexní čísla, dále čísla reálná, čísla racionální (zlomky) a nakonec čísla celá (nebo jen celá kladná či čísla přirozená):

bitcoin_skoleni

# Typ Význam
1 number libovolná obecná čísla
2 complex komplexní čísla
3 real reálná čísla
4 rational zlomky (racionální čísla)
5 integer celá čísla

Výše uvedená numerická věž může být rozšířena o další typy, což je případ programovacího jazyka Kawa, jenž tento koncept rozšiřuje o numerický typ kvaternion a taktéž (což je asi nejzajímavější a poměrně unikátní) o typ quantity, tedy o dvojice hodnota+jednotka (to je koncept, který možná čeká na své „znovuobjevení“):

Typ Význam
number libovolná obecná čísla
quantity numerická hodnota i s uvedenou jednotkou (viz další text)
quaternion kvaterniony
complex komplexní čísla
real reálná čísla
rational zlomky (racionální čísla)
integer celá čísla

19. Repositář s demonstračními příklady

Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python 3 a knihovnu libcst byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs:

# Demonstrační příklad Stručný popis příkladu Cesta
1 float0_1.py nepřesné uložení hodnoty 0,1 v typu float https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/float0_1.py
2 float_loop.py nekonečná počítaná programová smyčka s krokem přibližně 0,1 https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/float_loop.py
       
3 decimal0_1_A.py zdánlivě přesné uložení hodnoty 0,1, konstruktor používající float https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/de­cimal0_1_A.py
4 decimal0_1_B.py zdánlivě přesné uložení hodnoty 0,1, konstruktor používající řetězec https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/de­cimal0_1_B.py
5 decimal_loop_A.py nekonečná počítaná programová smyčka s krokem přibližně 0,1 https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/de­cimal_loop_A.py
6 decimal_loop_B.py konečná počítaná programová smyčka s krokem přesně 0,1 https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/de­cimal_loop_B.py
       
7 pi_wallis_float.py výpočet konstanty π z Wallisovy řady, realizace s typem float https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/pi_wa­llis_float.py
8 pi_wallis_decimal.py výpočet konstanty π z Wallisovy řady, realizace s typem Decimal https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/pi_wa­llis_decimal.py
9 pi_wallis_float_threshold.py výpočet konstanty π s nastavenou přesností, nekonečný výpočet https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/pi_wa­llis_float_threshold.py
10 pi_wallis_decimal_threshold.py výpočet konstanty π s nastavenou přesností, konečný výpočet https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/pi_wa­llis_decimal_threshold.py
       
11 harmonic_float.py výpočet harmonické řady, realizace s typem float https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/har­monic_float.py
12 harmonic_decimal.py výpočet harmonické řady, realizace s typem Decimal https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/har­monic_decimal.py
       
13 decimal_one_third.py hodnotu 1/3 není možné uložit zcela přesně ani v typu Decimal https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/de­cimal_one_third.py
14 sqrt.py odmocninu ze dvou není možné uložit zcela přesně ani v typu Decimal https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/sqrt.py
       
15 div_by_zero_A.py dělení nulou s vyhozením výjimky https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/div_by_ze­ro_A.py
16 div_by_zero_B.py dělení kladnou nulou bez vyhození výjimky https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/div_by_ze­ro_B.py
17 div_by_zero_B.py dělení zápornou nulou bez vyhození výjimky https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/div_by_ze­ro_B.py
18 div_zero_by_zero.py operace 0/0 https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/div_ze­ro_by_zero.py
       
19 infinity_A.py operace s nekonečny https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/infinity_A.py
20 infinity_B.py operace s nekonečny https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/infinity_B.py
21 infinity_C.py operace s nekonečny https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/infinity_C.py
22 infinity_D.py operace s nekonečny https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/infinity_D.py
23 infinity_E.py operace s nekonečny https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/infinity_E.py
       
24 power_A.py operace umocnění https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/power_A.py
25 power_B.py 00 https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/power_B.py
       
26 precision_A.py nastavení počtu cifer https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/pre­cision_A.py
26 precision_B.py nastavení počtu cifer https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/pre­cision_B.py
       
28 rounding.py změna zaokrouhlovacího režimu https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/rounding.py
       
29 traps_A.py zachytávání výjimek při výpočtech https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/traps_A.py
30 traps_B.py zachytávání výjimek při výpočtech https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/traps_B.py
31 traps_C.py zachytávání výjimek při výpočtech https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/traps_C.py
32 traps_D.py zachytávání výjimek při výpočtech https://github.com/tisnik/most-popular-python-libs/blob/master/decimal/traps_D.py

20. Odkazy na Internetu

  1. decimal — Decimal fixed point and floating point arithmetic
    https://docs.python.org/3/li­brary/decimal.html
  2. An Essential Guide to Python Decimal By Examples
    https://www.pythontutorial­.net/advanced-python/python-decimal/
  3. Extended real number line
    https://en.wikipedia.org/wi­ki/Extended_real_number_li­ne
  4. mathematical operations with infinity
    https://math.stackexchange­.com/questions/3969017/mat­hematical-operations-with-infinity
  5. GCC: 6.14 Decimal Floating Types
    https://gcc.gnu.org/online­docs/gcc/Decimal-Float.html
  6. Routines for decimal floating point emulation
    https://gcc.gnu.org/online­docs/gccint/Decimal-float-library-routines.html
  7. Representation of hexadecimal floating point
    https://www.ibm.com/docs/en/hla-and-tf/1.6?topic=lq-representation-hexadecimal-floating-point
  8. Decimal floating point
    https://en.wikipedia.org/wi­ki/Decimal_floating_point
  9. Hexadecimal Floating-Point Constants
    https://www.exploringbina­ry.com/hexadecimal-floating-point-constants/
  10. Norma IEEE 754 a příbuzní: formáty plovoucí řádové tečky
    https://www.root.cz/clanky/norma-ieee-754-a-pribuzni-formaty-plovouci-radove-tecky/
  11. decimal32 floating-point format
    https://en.wikipedia.org/wi­ki/Decimal32_floating-point_format
  12. decimal64 floating-point format
    https://en.wikipedia.org/wi­ki/Decimal64_floating-point_format
  13. decimal128 floating-point format
    https://en.wikipedia.org/wi­ki/Decimal128_floating-point_format
  14. IEEE-754 Floating-Point Conversion
    http://babbage.cs.qc.cuny.edu/IEEE-754.old/32bit.html
  15. Small Float Formats
    https://www.khronos.org/o­pengl/wiki/Small_Float_For­mats
  16. Binary-coded decimal
    https://en.wikipedia.org/wiki/Binary-coded_decimal
  17. Chen–Ho encoding
    https://en.wikipedia.org/wi­ki/Chen%E2%80%93Ho_encoding
  18. Densely packed decimal
    https://en.wikipedia.org/wi­ki/Densely_packed_decimal
  19. A Summary of Chen-Ho Decimal Data encoding
    http://speleotrove.com/decimal/chen-ho.html
  20. Art of Assembly language programming: The 80×87 Floating Point Coprocessors
    https://courses.engr.illi­nois.edu/ece390/books/arto­fasm/CH14/CH14–3.html
  21. Art of Assembly language programming: The FPU Instruction Set
    https://courses.engr.illi­nois.edu/ece390/books/arto­fasm/CH14/CH14–4.html
  22. INTEL 80387 PROGRAMMER'S REFERENCE MANUAL
    http://www.ragestorm.net/dow­nloads/387intel.txt
  23. Floating-Point Formats
    http://www.quadibloc.com/com­p/cp0201.htm
  24. IBM Floating Point Architecture
    http://en.wikipedia.org/wi­ki/IBM_Floating_Point_Archi­tecture
  25. Extended Binary Coded Decimal Interchange Code
    http://en.wikipedia.org/wiki/EBCDIC
  26. ASCII/EBCDIC Conversion Table
    http://docs.hp.com/en/32212–90008/apcs01.html
  27. EBCDIC
    http://www.hansenb.pdx.edu/DMKB/dic­t/tutorials/ebcdic.php
  28. EBCDIC tables
    http://home.mnet-online.de/wzwz.de/temp/eb­cdic/cc_en.htm
  29. 36-bit
    http://en.wikipedia.org/wiki/36-bit_word_length
  30. 36bit.org
    http://www.36bit.org/
  31. How did the Apple II do floating point?
    https://groups.google.com/fo­rum/#!topic/comp.emulator­s.apple2/qSBiG2TAlRg
  32. IBM Floating Point Architecture
    https://en.wikipedia.org/wi­ki/IBM_Floating_Point_Archi­tecture
  33. The Arithmetic Subroutines
    http://www.users.waitrose­.com/~thunor/mmcoyzx81/chap­ter17.html
  34. ZX Floating point to Decimal code in BASIC
    http://www.sinclairzxworld­.com/viewtopic.php?t=1422
  35. Floating Point Arithmetic Package
    http://www.retrocomputing­.net/parts/atari/800/docs/a­tari_os/atari_os_user_manu­al08.htm
  36. Turbo Pascal Real
    http://www.shikadi.net/mod­dingwiki/Turbo_Pascal_Real
  37. THE FLOATING POINT ARITHMETIC PACKAGE
    http://www.atarimax.com/fre­enet/freenet_material/5.8-BitComputersSupportArea/7­.TechnicalResourceCenter/sho­warticle.php?14
  38. The Most Expensive One-byte Mistake: Did Ken, Dennis, and Brian choose wrong with NUL-terminated text strings?
    http://queue.acm.org/deta­il.cfm?id=2010365

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.