|
163 | 163 | We only have about 15~digits, so doing something like~$10^{20} + 1$ will usually work out to just be~$10^{20}$ in floating point arithmetics~\cite{PTVF2007NRTAOSC:EAAS}. |
164 | 164 | But digits could also be lost when adding numbers of roughly the same scale, because their sum could just be larger so that the 15-digit-window shifts such that the least-significant digit falls off~\cite{BHK2006CNFCMEARFCS:NS}{\dots}% |
165 | 165 | % |
| 166 | +\FloatBarrier% |
166 | 167 | \endhsection% |
167 | 168 | % |
168 | 169 | \hsection{Back to Integers: Rounding}% |
|
232 | 233 | However, especially with the function \pythonilIdx{round}, we need to be careful. |
233 | 234 | It does \emph{not} necessarily work as one would expect!~({\dots}but arguably better.)% |
234 | 235 | % |
| 236 | +\FloatBarrier% |
235 | 237 | \endhsection% |
236 | 238 | % |
237 | 239 | \hsection{The Scientific Notation}% |
|
294 | 296 | However, if we write \pythonil{9_999_999_999_999_999.0}, something interesting happens: |
295 | 297 | We get \pythonil{1e+16}, i.e., $10^{16}$. |
296 | 298 | This is because of the limited precision of the floating point numbers, namely the 15~digits often mentioned above. |
| 299 | +We did specify 16~\inQuotes{9s} and therefore, it got rounded to the nearest 15\nobreakdashes-digit number, which is~$1*10^{16}$. |
297 | 300 | We cannot distinguish $10^{16}$ and $10^{16}-1$ when using \python's \pythonils{float}\pythonIdx{float}. |
298 | 301 | Indeed, writing \pythonil{10_000_000_000_000_000.0} also yields \pythonil{1e+16}.% |
299 | 302 | \end{sloppypar}% |
|
314 | 317 |
|
315 | 318 | You can even violate the scientific notation a bit when entering numbers if you feel naughty. |
316 | 319 | \pythonil{-12e30}, for example, would better be written as \pythonil{-1.2e+31}, which the \python\ console will do for you in its output. |
317 | | -Similarly, \pythonil{0.023e-20} becomes \pythonil{2.3e-22}. |
| 320 | +Similarly, \pythonil{0.023e-20} becomes \pythonil{2.3e-22}.% |
| 321 | +\FloatBarrier% |
318 | 322 | \endhsection% |
319 | 323 | % |
320 | 324 | \hsection{Limits and Special Floating Point Values: Infinity and \inQuotes{Not a Number}}% |
321 | 325 | \label{sec:float:special}% |
322 | 326 | % |
323 | 327 | % |
324 | 328 | \gitEvalPython{float_very_small}{}{simple_datatypes/float_very_small.py}% |
325 | | -\listingBox{exec:float_very_small}{What happens with very small floating point numbers in \python?\pythonIdx{0.0}.}{,style=python_console_style}% |
| 329 | +\listingBox{exec:float_very_small}{What happens with very small floating point numbers in \python?\pythonIdx{0.0}}{,style=python_console_style}% |
326 | 330 | % |
327 | 331 | We already learned that the floating point type \pythonilIdx{float} can represent both very small and very large numbers. |
328 | 332 | But we also know that it is internally stored as chunk of 64~bits. |
|
360 | 364 | They become zero. |
361 | 365 |
|
362 | 366 | \gitEvalPython{float_very_large}{}{simple_datatypes/float_very_large.py}% |
363 | | -\listingBox{exec:float_very_large}{What happens with very large floating point numbers in \python?\pythonIdx{inf}.}{,style=python_console_style,literate={0}{0\-}1 {1}{1\-}1 {2}{2\-}1 {3}{3\-}1 {4}{4\-}1 {5}{5\-}1 {6}{6\-}1 {7}{7\-}1 {8}{8\-}1 {9}{9\-}1,breakatwhitespace=false,breaklines=true} |
| 367 | +\listingBox{exec:float_very_large}{What happens with very large floating point numbers in \python?\pythonIdx{inf}}{,style=python_console_style,literate={0}{0\-}1 {1}{1\-}1 {2}{2\-}1 {3}{3\-}1 {4}{4\-}1 {5}{5\-}1 {6}{6\-}1 {7}{7\-}1 {8}{8\-}1 {9}{9\-}1,breakatwhitespace=false,breaklines=true} |
364 | 368 |
|
365 | 369 | But what happens to numbers that are too big for the available range? |
366 | 370 | Again, according to the nice documentation of \pgls{Java}~\cite{O2024JPSEJDKV2AS:CD}, the maximum 64~bit double precision floating point number value is~$(2-2^{-52})*2^{1023}\approx1.797\decSep693\decSep134\decSep862\decSep315\decSep708\dots*10^{308}$. |
367 | 371 | We can enter this value as \pythonilIdx{1.7976931348623157e+308}\pythonIdx{float!largest} and it indeed prints correctly in \cref{exec:float_very_large}. |
368 | 372 | If we step it up a little bit and enter \pythonil{1.7976931348623158e+308}, due to the limited precision, we again get \pythonilIdx{1.7976931348623157e+308}\pythonIdx{float!largest}. |
369 | 373 | However, if we try entering \pythonil{1.7976931348623159e+308} into the \python\ console, something strange happens: |
370 | 374 | The output is \pythonilIdx{inf}. |
371 | | -\pythonil{inf} means \emph{\inQuotes{too big to be represented as number with \pythonil{float},}} which obviously includes infinity~($+\infty$), but also simply all numbers bigger than~\pythonil{1.7976931348623158e+308}. |
372 | | - |
| 375 | +\pythonil{inf} means \emph{\inQuotes{too big to be represented as number with \pythonil{float},}} which obviously includes infinity~($+\infty$), but also simply all numbers bigger than~\pythonil{1.7976931348623158e+308}.% |
| 376 | +% |
| 377 | +\begin{sloppypar}% |
373 | 378 | \pythonil{-1.7976931348623159e+308} gives us \pythonilIdx{-inf}. |
374 | 379 | Multiplying this value by two, i.e., computing \pythonil{-1.7976931348623157e+308 * 2}, still yields \pythonil{-inf}. |
375 | 380 | Intuitively, based on its name, one would assume that \pythonilIdx{inf} stands for \inQuotes{infinity} or~$\infty$. |
376 | 381 | However, it \emph{actually} means \emph{too big to represent as \pythonilIdx{float} or~$\infty$}. |
377 | 382 | If we enter numbers that are too big or exceed the valid range of \pythonilIdx{float} by multiplication, addition, subtraction, or division, we simply get~\pythonilIdx{inf}. |
378 | 383 | This does not actually mean \inQuotes{mathematical infinity,} because, while \pythonil{-1.7976931348623159e+308}\pythonIdx{float!largest} is very big, it is not infinitely big. |
379 | | -It simply means that the number is too big to put it into a 64~bit \pythonilIdx{float}. |
380 | | - |
| 384 | +It simply means that the number is too big to put it into a 64~bit \pythonilIdx{float}.% |
| 385 | +\end{sloppypar}% |
| 386 | +% |
381 | 387 | Actually, the \pythonilIdx{int} type can represent larger numbers easily. |
382 | 388 | \pythonilIdx{1.7976931348623157e+308}\pythonIdx{float!largest} is equivalent to \pythonil{17_976_931_348_623_157 * 10 ** 292}. |
383 | 389 | This prints as a number with many zeros. |
|
408 | 414 | \gitEvalPython{float_nan}{}{simple_datatypes/float_nan.py}% |
409 | 415 | \listingBox{exec:float_nan}{Not a Number\pythonIdx{Not a Number}, i.e., \pythonilIdx{nan}.}{,style=python_console_style}% |
410 | 416 |
|
411 | | -Now, \pythonilIdx{inf} not actually being~$\infty$ is a little bit weird. |
| 417 | +Now, \pythonilIdx{inf} not actually being~$\infty$ is a little bit counter-intuitive. |
412 | 418 | But it can get even stranger, as you will see in \cref{exec:float_nan}. |
413 | 419 | \pythonilIdx{inf} is a number which is too big to be represented as \pythonilIdx{float} \emph{or}~$\infty$. |
414 | 420 | Once a variable has the value \pythonilIdx{inf}, however, it is treated as if it was actually infinity. |
|
432 | 438 | However, \pythonilIdx{nan} is \emph{really} different from really \emph{anything}. |
433 | 439 | Therefore, \pythonil{nan == nan}\pythonIdx{==} is \pythonilIdx{False} and \pythonil{nan != nan}\pythonIdx{"!=} is \pythonilIdx{True}! |
434 | 440 |
|
435 | | -Interestingly, we said that \pythonil{inf} behaves more or less like~$\infty$ in computations. |
436 | | -Except that \pythonil{inf == inf} is \pythonil{True}, which is strange, because \pythonil{inf - inf} is undefined. |
437 | | -But OK, let's just accept that somethings are a bit strange and move on. |
| 441 | +Interestingly, we can said that \pythonil{inf} behaves more or less like~$\infty$ in computations. |
| 442 | +It is \pythonil{True} that \pythonil{inf == inf} which makes total sense from a programming perspective. |
| 443 | +It may be considered a bit odd from a mathematical perspective, though, because infinity is not really a (single) value and there are different degrees of infinity: $\lim_{x\rightarrow\infty} x$~is different from~$\lim_{x\rightarrow\infty} x^2$ is different from~$\lim_{x\rightarrow\infty} \numberE^x$, for example. |
| 444 | +Then again, \pythonil{inf - inf} is undefined, which makes \inQuotes{more} mathematical sense, because we have no measure to know whether an \pythonil{inf} comes from something like \pythonil{1e308 * 2} or \pythonil{1e308 * 1e308}. |
| 445 | +But OK, let's just accept that somethings are a bit strange about \pythonil{inf} and move on. |
438 | 446 |
|
439 | 447 | \gitEvalPython{float_check_finite}{}{simple_datatypes/float_check_finite.py}% |
440 | 448 | \listingBox{exec:float_check_finite}{Checking for \pythonilIdx{nan} and \pythonilIdx{inf} via \pythonilIdx{isfinite}, \pythonilIdx{isinf}, and \pythonilIdx{isnan}.}{,style=python_console_style}% |
441 | 449 |
|
442 | 450 | Either way, the possible occurrence \pythonilIdx{inf} and \pythonilIdx{nan} in floating point computations is a cumbersome issue. |
443 | | -If we want to further process results of a computation, we often want to make sure that it is neither \pythonilIdx{inf} nor \pythonilIdx{-inf} nor \pythonilIdx{nan}. |
| 451 | +If you think about it: |
| 452 | +There hardly is any scenario where \pythonilIdx{inf} is a reasonable result of a computation. |
| 453 | +Looking back at \cref{exec:float_very_large,exec:float_nan}, we had to do some rather strange wrenches to get them to occur. |
| 454 | +There are four probable reasons why these values could occur:% |
| 455 | +% |
| 456 | +\begin{enumerate}% |
| 457 | +% |
| 458 | +\item Because they make total sense and are the expected result of correct computations with correct data. |
| 459 | +Does this sound very likely to you?% |
| 460 | +% |
| 461 | +\item Because some of the input data of our computations is incorrect, corrupted, or faulty.% |
| 462 | +% |
| 463 | +\item Because we have some error in our computation, maybe a typo, maybe misplaced parentheses, maybe a mathematical misconception.% |
| 464 | +% |
| 465 | +\item Because someone deliberately sent corrupted data to the input of our computation in order to provoke unexpected or unintented behavior.% |
| 466 | +% |
| 467 | +\end{enumerate}% |
| 468 | +% |
| 469 | +The first possible reasons indeed seems to be unlikely. |
| 470 | +There probably are few situations where \pythonilIdx{nan} is a value that you would expect and want to get and work with. |
| 471 | +Very often, if such values occur, something went wrong. |
| 472 | +Maybe some input value was wrong. |
| 473 | +Maybe some value was supplied that was too large or too small to be reasonable. |
| 474 | +Maybe a 0 occured in a place where no 0 should occur. |
| 475 | +Maybe we just made an error when writing down the formula. |
| 476 | + |
| 477 | +Also, let's not just consider \inQuotes{errors} here. |
| 478 | +It is not entirely impossible that someone tried to \emph{intentionally} and maliciously manipulate the input of our program. |
| 479 | +Maybe someone tries to trigger some otherwise impossible behavior of our code\dots |
| 480 | +Values like \pythonilIdx{nan} can be a real security concern~\cite{C2026ROT2UCCAOONPA}. |
| 481 | + |
| 482 | +Therefore, if we want to further process results of a computation where such a thing might happen for one reasone or another, we often want to make sure that it is neither \pythonilIdx{inf} nor \pythonilIdx{-inf} nor \pythonilIdx{nan}. |
444 | 483 | This can be done via the function \pythonilIdx{isfinite}, which we can import from the \pythonilIdx{math} module\pythonIdx{import}\pythonIdx{from}, as you can see in \cref{exec:float_check_finite}. |
445 | 484 | \pythonil{1e34} is a large number, but \pythonil{isfinite(1e34)} is certainly \pythonilIdx{True}. |
446 | 485 | \pythonil{isfinite(inf)} and \pythonil{isfinite(nan)} are both \pythonilIdx{False}. |
|
0 commit comments