Skip to content

Commit f15c813

Browse files
committed
further improved float section
1 parent 5d859c9 commit f15c813

File tree

2 files changed

+52
-13
lines changed

2 files changed

+52
-13
lines changed

text/main/basics/simpleDataTypesAndOperations/float/float.tex

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@
163163
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}.
164164
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}%
165165
%
166+
\FloatBarrier%
166167
\endhsection%
167168
%
168169
\hsection{Back to Integers: Rounding}%
@@ -232,6 +233,7 @@
232233
However, especially with the function \pythonilIdx{round}, we need to be careful.
233234
It does \emph{not} necessarily work as one would expect!~({\dots}but arguably better.)%
234235
%
236+
\FloatBarrier%
235237
\endhsection%
236238
%
237239
\hsection{The Scientific Notation}%
@@ -294,6 +296,7 @@
294296
However, if we write \pythonil{9_999_999_999_999_999.0}, something interesting happens:
295297
We get \pythonil{1e+16}, i.e., $10^{16}$.
296298
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}$.
297300
We cannot distinguish $10^{16}$ and $10^{16}-1$ when using \python's \pythonils{float}\pythonIdx{float}.
298301
Indeed, writing \pythonil{10_000_000_000_000_000.0} also yields \pythonil{1e+16}.%
299302
\end{sloppypar}%
@@ -314,15 +317,16 @@
314317

315318
You can even violate the scientific notation a bit when entering numbers if you feel naughty.
316319
\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%
318322
\endhsection%
319323
%
320324
\hsection{Limits and Special Floating Point Values: Infinity and \inQuotes{Not a Number}}%
321325
\label{sec:float:special}%
322326
%
323327
%
324328
\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}%
326330
%
327331
We already learned that the floating point type \pythonilIdx{float} can represent both very small and very large numbers.
328332
But we also know that it is internally stored as chunk of 64~bits.
@@ -360,24 +364,26 @@
360364
They become zero.
361365

362366
\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}
364368

365369
But what happens to numbers that are too big for the available range?
366370
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}$.
367371
We can enter this value as \pythonilIdx{1.7976931348623157e+308}\pythonIdx{float!largest} and it indeed prints correctly in \cref{exec:float_very_large}.
368372
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}.
369373
However, if we try entering \pythonil{1.7976931348623159e+308} into the \python\ console, something strange happens:
370374
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}%
373378
\pythonil{-1.7976931348623159e+308} gives us \pythonilIdx{-inf}.
374379
Multiplying this value by two, i.e., computing \pythonil{-1.7976931348623157e+308 * 2}, still yields \pythonil{-inf}.
375380
Intuitively, based on its name, one would assume that \pythonilIdx{inf} stands for \inQuotes{infinity} or~$\infty$.
376381
However, it \emph{actually} means \emph{too big to represent as \pythonilIdx{float} or~$\infty$}.
377382
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}.
378383
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+
%
381387
Actually, the \pythonilIdx{int} type can represent larger numbers easily.
382388
\pythonilIdx{1.7976931348623157e+308}\pythonIdx{float!largest} is equivalent to \pythonil{17_976_931_348_623_157 * 10 ** 292}.
383389
This prints as a number with many zeros.
@@ -408,7 +414,7 @@
408414
\gitEvalPython{float_nan}{}{simple_datatypes/float_nan.py}%
409415
\listingBox{exec:float_nan}{Not a Number\pythonIdx{Not a Number}, i.e., \pythonilIdx{nan}.}{,style=python_console_style}%
410416

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.
412418
But it can get even stranger, as you will see in \cref{exec:float_nan}.
413419
\pythonilIdx{inf} is a number which is too big to be represented as \pythonilIdx{float} \emph{or}~$\infty$.
414420
Once a variable has the value \pythonilIdx{inf}, however, it is treated as if it was actually infinity.
@@ -432,15 +438,48 @@
432438
However, \pythonilIdx{nan} is \emph{really} different from really \emph{anything}.
433439
Therefore, \pythonil{nan == nan}\pythonIdx{==} is \pythonilIdx{False} and \pythonil{nan != nan}\pythonIdx{"!=} is \pythonilIdx{True}!
434440

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.
438446

439447
\gitEvalPython{float_check_finite}{}{simple_datatypes/float_check_finite.py}%
440448
\listingBox{exec:float_check_finite}{Checking for \pythonilIdx{nan} and \pythonilIdx{inf} via \pythonilIdx{isfinite}, \pythonilIdx{isinf}, and \pythonilIdx{isnan}.}{,style=python_console_style}%
441449

442450
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}.
444483
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}.
445484
\pythonil{1e34} is a large number, but \pythonil{isfinite(1e34)} is certainly \pythonilIdx{True}.
446485
\pythonil{isfinite(inf)} and \pythonil{isfinite(nan)} are both \pythonilIdx{False}.

0 commit comments

Comments
 (0)