<style> .red { color: red; } .orange { color: #f99157; } </style> ![](https://i.imgur.com/7llNQqU.jpg =x400) aneb _Třeba to půjde, když změním epsilon_ Vašek Volhejn @ Výběrko 2021 --- ## Sčítání popředu a pozadu ---- ### Kód - Python ```python import random random.seed(1) a = [random.random() for _ in range(1000)] print("a =", a[:5], "...") print() print("Soucet a: ", sum(a)) print("Soucet a, pozadu:", sum(reversed(a))) print("Jsou stejne?", sum(a) == sum(reversed(a))) print("Rozdil:", sum(a) - sum(reversed(a))) ``` --- ### Problém - konečný počet bitů = konečný počet možností - ale čísel je nekonečně, chyby budou --- ### Absolutní/relativní odchylka Číslo $x$, jeho počítačová reprezentace $y$ Absolutní odchylka: $|x-y|$ Relativní odchylka: $\Big| \frac{x-y}{x} \Big|$ --- ### První pokus: fixed-point Fixní počet čísel před a za desetinnou tečkou: ``` 1010.0011 + 0001.1001 = 0011.1100 ``` Absolutní odchylka do 0.03125, relativní se mění Jenže co hodně malá nebo hodně velká čísla? --- ### Floating-point Řešení: zapamatovat si jen ty nejdůležitější bity a pozici desetinné tečky <pre> x = 0000000<b><span style="color: red">1</span><span style="color: #f99157">0110.1110</span></b>11110100... y = 000000000000.00<b><span style="color: red">1</span><span style="color: #f99157">11000101</span></b>1... </pre> $x \approx 1.01101110 \cdot 2^{4}$ $y \approx 1.11000101 \cdot 2^{-3}$ Relativní odchylka do ~0.002, absolutní se mění --- ### Floating-point reprezentace Tvar $a \cdot 2^b$, kde $b$ je celé, $1 \leq a < 2$ Např: - 8 bitů pro $a$ = rozsah <code><span style="color: red">1</span>.<span style="color: #f99157">00000000</span></code> až <code><span style="color: red">1</span>.<span style="color: #f99157">11111111</span></code> - rozsah $b$ od $-8$ do $7$, takže 4 bity --- <code> x = 0000000<b><span style="color: red">1</span><span style="color: #f99157">0110.1110</span></b>11110100... </code> reprezentujeme jako <code><span style="color: #f99157">01101110</span> 1100 </code> --- ### Sčítání <code> <b><span class="red">1</span>.<span class="orange">01110101</span></b> ⋅ 2<sup>3</sup> + <b><span class="red">1</span>.<span class="orange">10010110</span></b> ⋅ 2<sup>-1</sup> </code> <pre> <b><span class="red">1</span><span class="orange">011.10101</span></b> + 0.<b><span class="red">1</span><span class="orange">10010110</span></b> = <b><span class="red">1</span><span class="orange">100.01110</span></b>0110 </pre> <code>≈ <b><span class="red">1</span>.<span class="orange">01110101</span></b> ⋅ 2<sup>3</sup></code> --- ### Násobení <code> <b><span class="red">1</span>.<span class="orange">01110101</span></b> ⋅ 2<sup>3</sup> x <b><span class="red">1</span>.<span class="orange">10010110</span></b> ⋅ 2<sup>-1</sup> = </code> <code> <b><span class="red">1</span>.<span class="orange">01110101</span></b> x <b><span class="red">1</span>.<span class="orange">10010110</span></b> ⋅ 2<sup>3 + (-1)</sup> = </code> <code> <b><span class="red">1</span>.<span class="orange">10010011</span></b>1110001110 ⋅ 2<sup>2</sup> </code> <code>≈ <b><span class="red">1</span>.<span class="orange">10010100</span></b> ⋅ 2<sup>2</sup></code> --- #### Skutečné floating-point typy ([IEEE 754](https://en.wikipedia.org/wiki/IEEE_754)) $x \approx a \cdot 2^b$ | C++ typ | Bity | Bity $a$ (mantisa) | Bity $b$ (exponent) | Rozsah | | ------------- | ---- | ------------------ | ------------------- | --- | | `float` | 32 | 24 | 8 | $\pm 1.17 · 10^{-38}$ až $\pm 3.40 · 10^{38}$ | | `double` | 64 | 53 | 11 | $\pm 2.25 · 10^{-308}$ až $\pm 1.80 · 10^{308}$ | --- ### Co se může pokazit Problém: operace s čísly na různých škálách $x^2 - y^2 = (x-y) \cdot (x+y)$ V doublech nemusí platit! ---- ```python >>> x=1e9+1 # 1e9 znamena 10^9 >>> y=1e9 >>> (x-y)*(x+y) 2000000001.0 >>> x**2 - y**2 2000000000.0 ``` ---- \begin{align} x^2 &= 10^{18} + 2 \cdot 10^9 + 1 \\ &\approx 10^{18} + 2 \cdot 10^9 \\ y^2 &= 10^{18} \\ x^2 - y^2 &\approx 2 \cdot 10^9 \end{align} ![](https://i.imgur.com/Vhx5uNG.png) --- ### Co se může pokazit 2 Jak zjistit, na jaké straně přímky je bod? ![](https://i.imgur.com/qucJEAv.png =x200) Znaménko hodnoty $(x_q - x_p)(y_r - y_p) - (y_q - y_p)(x_r - x_p)$ Pro $p = (0, 0)$: $x_qy_r - y_qx_r$ ---- $(x_q - x_p)(y_r - y_p) - (y_q - y_p)(x_r - x_p)$ ![](https://i.imgur.com/WThLn4O.png) ---- $(x_q - x_p)(y_r - y_p) - (y_q - y_p)(x_r - x_p)$ ![](https://i.imgur.com/YSdlaRT.png) ---- Poučení: v celých číslech super, v doublech peklo --- ### Co s tím na soutěži? ---- #### Porovnávat s odchylkou Číslo `x` může znamenat cokoliv v intervalu `(x-eps, x+eps)`, `eps` typicky mezi `1e-9` a `1e-6` | Před | Po | | -------- | -------- | | `x==y` | `abs(x-y)<eps` | | `x<y` | `x<y-eps` | | `x<=y` | `x<y+eps` | <!-- TODO ilustrace --> ---- #### Zmenšit počet nepřesných operací Když počítám $\frac{1}{2} \cdot \frac{1}{3} \cdot \frac{1}{5}$, můžu místo `1.0/2.0/3.0/5.0` napsat `1.0/(2*3*5)` ---- #### Dávat pozor na hranice celých čísel Místo `floor(x)` radši `floor(x+eps)` Místo `sqrt(x)` radši `sqrt(max(x, 0))` ---- #### Neodmocňovat - Podmínku `i < sqrt(n)` můžeme přepsat na `i*i < n` - Geometrické vzdálenosti: pokud záleží jen na pořadí, můžeme místo $\sqrt{x^2 + y^2}$ porovnávat $x^2 + y^2$ - Podobně: řazení podle úhlu <!-- TODO ilustrace --> ---- #### Počítat pomocí zlomků - Čísla tvaru $\frac{p}{q}$ můžeme reprezentovat jako dvě celá čísla $p$ a $q$, aritmetika $\frac{p_1}{q_1} + \frac{p_2}{q_2} = \frac{p_1q_2 + p_2q_1}{q_1q_2}$ apod. - Pozor na overflow! ---- #### Vypisovat s dostatečnou přesností ```c++ #include <iomanip> ... cout << fixed << setprecision(9) << x; printf("%.20f", x); ``` ---- #### Pokud k dispozici: arbitrary-precision Java: `BigDecimal` Python: `Decimal` --- ### Další zákeřnosti ``` 1. / 0. == inf -1. / 0. == -inf inf == inf + 1e9 inf - inf == nan 0. / 0. == nan nan != 3 nan != inf nan != nan (!) ``` --- ### Toť vše https://kasiopea.matfyz.cz/archiv/2017/doma/F/ https://kasiopea.matfyz.cz/archiv/2018/doma/D/ Hlavní zdroj: Kurz [Competitive Programmer's Core Skills](https://www.coursera.org/lecture/competitive-programming-core-skills/floating-point-numbers-qdtKK) --- úvod přes fixed-point čísla logaritmická škála Není asociativní! Brání optimalizacím divnosti jako nan a nekonečno; plati inf>konecne x, inf+x=inf inf - inf = -nan ("nedefinovano"), nan != nan, sqrt(1e-9) = nan <- pozor! fast inverse square root long double arbitrary precision (Java: BigDecimal, Python: Decimal?) nejcasteji reportovany GCC bug https://www.coursera.org/lecture/competitive-programming-core-skills/floating-point-numbers-qdtKK ukazka s mensim poctem bitu v mantise a exponentu x^2 - y^2 = (x-y)*(x+y) pro y=1e9, x=y+1 prvni je horsi, ma chybu o 1 vyhnuti se doublum pomoci zlomku neodmocnovat: i*i < n misto i<sqrt(n), porovnavani delek delat double operace az na konci: 1/2/3/5 = 1/(2*3*5) porovnavat s epsilonem, x < y na x < y - eps, x <= y na x < y + eps, floor rozumne hodnoty eps: 1e-9 az 1e-6
{"metaMigratedAt":"2023-06-15T23:02:32.848Z","metaMigratedFrom":"YAML","title":"Kouzelný svět doublů","breaks":true,"slideOptions":"{\"theme\":\"solarized\"}","contributors":"[{\"id\":\"caca0e12-4764-4027-b8fb-3f0c57f3440c\",\"add\":11470,\"del\":3994}]"}
    232 views