{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": { "cell_style": "center", "hide_input": true, "slideshow": { "slide_type": "skip" }, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "%matplotlib inline\n", "%config InlineBackend.figure_format='retina'\n", "# import libraries\n", "import numpy as np\n", "import matplotlib as mp\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "import laUtilities as ut\n", "import slideUtilities as sl\n", "import demoUtilities as dm\n", "import pandas as pd\n", "from importlib import reload\n", "from datetime import datetime\n", "from IPython.display import Image\n", "from IPython.display import display_html\n", "from IPython.display import display\n", "from IPython.display import Math\n", "from IPython.display import Latex\n", "from IPython.display import HTML" ] }, { "cell_type": "markdown", "metadata": { "hide_input": true, "slideshow": { "slide_type": "slide" } }, "source": [ "# (Getting Serious About) Numbers" ] }, { "cell_type": "raw", "metadata": {}, "source": [] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "```{margin}\n", "Photo Credit: \n", "George M. Bergman, CC BY-SA 4.0, via Wikimedia Commons\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "\"Figure\"\n", " \n", "
\n", " William Kahan, creator of the IEEE-754 standard.\n", "
\n", "Turing Award Winner, 1989\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": { "hide_input": true }, "source": [ ">I have a number in my head
\n", ">Though I don't know why it's there
\n", ">When numbers get serious
\n", ">You see their shape everywhere\n", ">\n", ">Paul Simon" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "One of the themes of this course will be shifting between mathematical and computational views of various concepts.\n", "\n", "Today we need to talk about why the answers we get from computers can be different from the answers we get mathematically \n", "\n", "-- for the same question!" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The root of the problem has to do with how __numbers__ are manipulated in a computer.\n", "\n", "In other words, how numbers are __represented.__" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Representations" ] }, { "cell_type": "markdown", "metadata": { "cell_style": "split", "slideshow": { "slide_type": "-" } }, "source": [ "A number is a mathematical concept -- an abstract idea. \n" ] }, { "cell_type": "markdown", "metadata": { "cell_style": "split", "slideshow": { "slide_type": "fragment" } }, "source": [ "> God made the integers,
all else is the work of man.\n", ">\n", "> Leopold Kronecker (1823 - 1891)" ] }, { "cell_type": "markdown", "metadata": { "cell_style": "center", "slideshow": { "slide_type": "fragment" } }, "source": [ "\n", "In a computer we assign __bit patterns__ to correspond to certain numbers. \n", "\n", "We say the bit pattern is the number's _representation._" ] }, { "cell_type": "markdown", "metadata": { "cell_style": "center", "slideshow": { "slide_type": "fragment" } }, "source": [ "For example the number '3.14' might have the representation '01000000010010001111010111000011'.\n", "\n", "For reasons of efficiency, we use a fixed number of bits for these representations. In most computers nowadays we use __64 bits__ to represent a number. " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Let's look at some number representations and see what they imply about computations." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Integers" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Kronecker believed that integers were the only 'true' numbers.\n", "\n", "And for the most part, using integers in a computer is not complicated. \n", "\n", "Integer representation is essentially the same as binary numerals. \n", "\n", "For example, in a 64-bit computer, the representation of the concept of 'seven' would be '0..0111' (with 61 zeros in the front).\n", "\n", "There is a size limit on the largest value that can be stored as an integer, but it's so big we don't need to concern ourselves with it in this course." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "So for our purposes, an integer can be stored exactly.\n", "\n", "In other words, there is an 1-1 correspondence between every (computational) representation and the corresponding (mathematical) integer." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "So, what happens when we compute with integers?\n", "\n", "For (reasonably sized) integers, computation is __exact__ .... as long as it only involves __addition, subtraction, and multiplication.__ \n", "\n", "In other words, there are no errors introduced when adding, subtracting or multiplying integers. " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "However, it is a different story when we come to division, because the integers are not closed under division.\n", "\n", "For example, 2/3 is not an integer. ... It is, however, a __real__ number." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Real Numbers and Floating-Point Representations" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Representing a real number in a computer is a __much__ more complicated matter. \n", "\n", "In fact, for many decades after electronic computers were developed, there was no accepted \"best\" way to do this! \n", "\n", "Eventually (in the 1980s) a widely accepted standard emerged, called IEEE-754. This is what almost all computers now use." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" }, "tags": [] }, "source": [ "The style of representation used is called __floating point.__" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" }, "tags": [] }, "source": [ "Conceptually, it is similar to \"scientific notation.\"\n", "\n", "$$123456 = \\underbrace{1.23456}_{\\text{significand}}\\times {\\underbrace{10}_{\\text{base}}}^{\\overbrace{5}^{exponent}}$$" ] }, { "cell_type": "markdown", "metadata": { "cell_style": "center", "slideshow": { "slide_type": "fragment" } }, "source": [ "Except that it is encoded in binary:\n", "\n", "$$17 = \\underbrace{1.0001}_{\\text{significand}}\\times {\\underbrace{2}_{\\text{base}}}^{\\overbrace{4}^{exponent}}$$" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The sign, significand, and exponent are all contained within the 64 bits." ] }, { "cell_type": "markdown", "metadata": { "hide_input": true, "slideshow": { "slide_type": "skip" }, "tags": [] }, "source": [ "```{margin}\n", "By Codekaizen (Own work) [GFDL or CC BY-SA 4.0-3.0-2.5-2.0-1.0], via Wikimedia Commons\n", "```" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "hide_input": true, "slideshow": { "slide_type": "-" }, "tags": [ "remove-input" ] }, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "" ] }, "metadata": { "image/png": { "width": 450 } }, "output_type": "display_data" } ], "source": [ "display(Image(\"images/IEEE_754_Double_Floating_Point_Format.png\", width=450))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Because only a fixed number of bits are used, __most real numbers cannot be represented exactly in a computer.__\n", "\n", "Another way of saying this is that, usually, a floating point number is an approximation of some particular real number." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Generally when we try to store a real number in a computer, __what we wind up storing is the closest floating point number that the computer can represent.__" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### The Relative Error of a Real Number stored in a Computer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "````{margin}\n", "```{note}\n", "You can experiment with floating point representations to see how errors arise using [this interactive tool](https://baseconvert.com/ieee-754-floating-point). \n", "```\n", "````" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The way to think about working with floating point (in fact, how the hardware actually does it) is:\n", "\n", "1. Represent each input as the __nearest__ representable floating point number.\n", "2. Compute the result exactly from the floating point representations.\n", "3. Return the __nearest__ representable floating point number to the result." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "What does \"__nearest__\" mean? Long story short, it means \"round to the nearest representable value.\"\n", "\n", "Let's say we have a particular real number $r$ and we represent it as a floating point value $f$. \n", "\n", "Then $r = f + \\epsilon$ where $\\epsilon$ is the amount that $r$ was rounded when represented as $f$." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "So $\\epsilon$ is the difference between the value we want, and the value we get.\n", "\n", "How big can this difference be? Let's say $f$ is\n", "\n", "$$f = \\underbrace{1.010...01}_\\text{53 bits}\\times 2^n$$" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Then $|\\epsilon|$ must be smaller than \n", "\n", "$$|\\epsilon| < \\underbrace{0.000...01}_\\text{53 bits}\\times 2^n.$$" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "So as a _relative error_, \n", "\n", "$$ \\text{relative error} = \\frac{|\\epsilon|}{f} < \\frac{{0.000...01}\\times 2^n}{\\underbrace{1.000...00}_\\text{53 bits}\\times 2^n} = 2^{-52} \\approx 10^{-16}$$" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "This value $10^{-16}$ is an important one to remember.\n", "\n", "It is approximately __the relative error that can be introduced any time a real number is stored in a computer.__" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Another way of thinking about this is that you __only have about 16 digits of accuracy__ in a floating point number." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Implications of Representation Error" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Problems arise when we work with floating point numbers and confuse them with real numbers.\n", "\n", "It is important to remember that most of the time we are not storing the real number exactly, but only a floating point number that is close to it." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Let's look at some examples. First:\n", "\n", "$$ \\left( \\frac{1}{8} \\cdot 8 \\right) - 1 $$" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "0.0" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# ((1/8)*8)-1\n", "a = 1/8\n", "b = 8\n", "c = 1\n", "(a*b)-c" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "It turns out that 1/8, 8, and 1 can all be stored exactly in IEEE-754 floating point format.\n", "\n", "So, we are \n", "* storing the inputs exactly (1/8, 8 and 1)\n", "* computing the results exactly (by definition of IEEE-754), yielding $(1/8) \\cdot 8 = 1$\n", "* and representing the result exactly (zero)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "OK, here is another example:\n", "\n", "$$ \\left( \\frac{1}{7} \\cdot 7 \\right) - 1 $$" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.0" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# ((1/7)*7)-1\n", "a = 1/7\n", "b = 7\n", "c = 1\n", "a * b - c" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Here the situation is different. \n", "\n", "1/7 can __not__ be stored exactly in IEEE-754 floating point format.\n", "\n", "In binary, 1/7 is $0.001\\overline{001}$, an infinitely repeating pattern that obviously cannot be represented as a finite sequence of bits." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Nonetheless, the computation $(1/7) \\cdot 7$ still yields exactly 1.0.\n", "\n", "Why? Because the rounding of $0.001\\overline{001}$ to its closest floating point representation, when multiplied by 7, yields a value whose closest floating point representation is 1.0." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Now, let's do something that seems very similar:\n", "\n", "$$ \\left( \\frac{1}{70} \\cdot 7 \\right) - 0.1 $$" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "-1.3877787807814457e-17" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = 1/70\n", "b = 7\n", "c = 0.1\n", "a * b - c" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "In this case, both 1/70 and 0.1 __cannot__ be stored exactly. \n", "\n", "More importantly, the process of rounding 1/70 to its closest floating point representation, then multiplying by 7, yields a number whose closest floating point representation is __not__ 0.1" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "However, that floating point representation is very __close__ to 0.1. \n", "\n", "Let's look at the difference: -1.3877787807814457e-17. \n", "\n", "This is about $-1 \\cdot 10^{-17}$." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "In other words, about -0.00000000000000001\n", "\n", "Compared to 0.1, this is a very small number. The relative error is about:\n", "\n", "$$ \\frac{|-0.00000000000000001|}{0.1} $$\n", "\n", "which is about $10^{-16}.$" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "This suggests that when a floating point calculation is not exact, the error (in a relative sense) is usually very small." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Notice also that in our example the size of the relative error is about $10^{-16}$. \n", "\n", "Recall that the significand in IEEE-754 uses 52 bits.\n", "\n", "Now, note that $2^{-52} \\approx 10^{-16}$.\n", "\n", "There's our \"sixteen digits of accuracy\" principle again." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Special Values" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "There are three kinds of special values defined by IEEE-754:\n", "1. NaN, which means \"Not a Number\" \n", "2. Infinity -- both positive and negative\n", "3. Zero -- both positive and negative." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "__NaN__ and __Inf__ behave about as you'd expect. \n", "\n", "If you get one of these values in a computation you should be able to reason about how it happened. Note that these are values, and can be assigned to variables." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/var/folders/d9/_sfhw3kd21dgyrgz6tbt45z80000gn/T/ipykernel_61048/3438155168.py:1: RuntimeWarning: invalid value encountered in sqrt\n", " np.sqrt(-1)\n" ] }, { "data": { "text/plain": [ "nan" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.sqrt(-1)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/var/folders/d9/_sfhw3kd21dgyrgz6tbt45z80000gn/T/ipykernel_61048/329155313.py:1: RuntimeWarning: divide by zero encountered in log\n", " var = np.log(0)\n" ] }, { "data": { "text/plain": [ "-inf" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var = np.log(0)\n", "var" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "-0.0" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "1/var" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "As far as we are concerned, there is no difference between positive and negative zero. You can ignore the minus sign in front of a negative zero. (If you are curious why there is a negative zero, see the online notes.)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "````{margin}\n", "```{note}\n", "\n", "The reason for having a negative and positive zero is the following. \n", "\n", "Remember that, due to the limitations of floating point representation, we can only store the __nearest representable__ number to the one we'd like to store.\n", "\n", "So, let's say we try to store a number $x$ that is very close to zero. To be specific, let $|x| < 2.2 \\times 10^{-308}$. Then the closest floating point representation is zero, so that is what is stored. This is known as \"underflow\".\n", "\n", "But ... the number $x$ that we were _trying_ to store could have been positive or negative. So the standard defines a positive and negative zero. The sign of zero tells us when underflow occurred, \"which direction\" the underflow came from. \n", "\n", "This can be useful in some numerical algorithms.\n", "```\n", "````" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "nan" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var = np.nan\n", "var + 7" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "inf" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var = np.inf\n", "var + 7" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## _Mathematical_ Computation vs. _Mechanical_ Computation" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "In a mathematical theorem, working with (idealized) numbers, it is always true that:\n", "\n", "If \n", "\n", "$$c = 1/a$$ \n", "\n", "then \n", "\n", "$$abc = b.$$\n", "\n", "In other words, \n", "\n", "$$(ab)/a = b.$$" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Let's test whether this is always true in actual computation." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "0.1" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = 7\n", "b = 1/10\n", "c = 1/a\n", "a*c*b" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "0.09999999999999999" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "b*c*a" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a*c*b == b*c*a" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Here is another example:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.30000000000000004" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "0.1 + 0.1 + 0.1 " ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5.551115123125783e-17" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "3 * (0.1) - 0.3" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "__What does all this mean for us in practice?__\n", "\n", "I will now give you three principles to keep in mind when computing with floating point numbers." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Principle 1: Do not compare floating point numbers for equality" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Two floating point computations that _should_ yield the same result mathematically, may not do so due to rounding error.\n", "\n", "However, in general, if two numbers should be equal, the relative error of the difference in the floating point should be small.\n", "\n", "So, instead of asking whether two floating numbers are equal, we should ask whether the relative error of their difference is small." ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "1.3877787807814457e-16" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "r1 = a * b * c\n", "r2 = b * c * a\n", "np.abs(r1-r2)/r1" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "finfo(resolution=1e-15, min=-1.7976931348623157e+308, max=1.7976931348623157e+308, dtype=float64)" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.finfo('float')" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "False\n" ] } ], "source": [ "print(r1 == r2)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n" ] } ], "source": [ "print(np.abs(r1 - r2)/np.max([r1, r2]) < np.finfo('float').resolution)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This test is needed often enough that `numpy` has a function that implements it:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.isclose(r1, r2)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "So another way to state Rule 1 for our purposes is:\n", " \n", "... __always__ use `np.isclose()` to compare floating point numbers for equality!" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Next, we will generalize this idea a bit: \n", "\n", "beyond the fact that numbers that should be equal, may not be in practice, \n", "\n", "we can also observe that it can be hard to be accurate about the __difference__ between two numbers that are __nearly__ equal. This leads to the next two principles." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Principle 2: Beware of ill-conditioned problems" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "An __ill-conditioned__ problem is one in which the outputs depend in a very sensitive manner on the inputs. \n", "\n", "That is, a small change in the inputs can yield a very large change in the outputs.\n", "\n", "The simplest example is computing $1/(a-b)$." ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "r1 is 0.1\n", "r2 is very close to r1\n", "r3 is 0.1001\n" ] } ], "source": [ "print(f'r1 is {r1}')\n", "print(f'r2 is very close to r1')\n", "r3 = r1 + 0.0001\n", "print(f'r3 is 0.1001')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's look at\n", "\n", "$$ \\frac{1}{r1 - r2} \\text{ versus } \\frac{1}{r3-r2} $$" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1/(r1 - r2) = 7.205759403792794e+16\n", "1/(r3 - r2) = 9999.999999998327\n" ] } ], "source": [ "print(f'1/(r1 - r2) = {1/(r1 - r2)}')\n", "print(f'1/(r3 - r2) = {1/(r3 - r2)}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If $a$ is close to $b$, small changes in either make a big difference in the output." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Because the inputs to your problem may not be exact, if the problem is ill-conditioned, the outputs may be wrong by a large amount." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Later on we will see that the notion of ill-conditioning applies to matrix problems too, and in particular comes up when we solve certain problems involving matrices." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Principle 3: Relative error can be magnified during subtractions" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Two numbers, each with small relative error, can yield a value with large relative error if subtracted." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Let's say we represent a = 1.2345 as 1.2345002 -- the relative error is 0.0000002." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Let's say we represent b = 1.234 as 1.2340001 -- the relative error is 0.0000001." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Now, subtract a - b: the result is .0005001. " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "What is the relative error? 0.005001 - 0.005 / 0.005 = 0.0002 " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The relative error of the result is 1000 times larger than the relative error of the inputs." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Here's an example in practice:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "9e-08\n", "8.999999989711682e-08\n" ] } ], "source": [ "a = 1.23456789\n", "b = 1.2345678\n", "print(0.00000009)\n", "print(a-b)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1.1431464011915431e-09\n" ] } ], "source": [ "print(np.abs(a-b-0.00000009)/ 0.00000009)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We know the relative error in the inputs is on the order of $10^{-16}$, but the relative error of the output is on the order of $10^{-9}$ -- i.e., a million times larger." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Further Reading" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "* Further information about how Python handles issues around floating point is at [Floating Point Arithmetic: Issues and Limitations](https://docs.python.org/3/tutorial/floatingpoint.html). \n", "* An excellent, in-depth introduction to floating point is [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html)." ] } ], "metadata": { "celltoolbar": "Slideshow", "foo": "a", "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.16" }, "rise": { "chalkboard": { "color": [ "rgba(250, 0, 0, 1)", "rgba(0, 250, 0, 1)" ] }, "enable_chalkboard": true, "scroll": true, "theme": "beige", "transition": "fade" } }, "nbformat": 4, "nbformat_minor": 4 }