lots of cleanup
This commit is contained in:
1 parent
ae461659e0
commit
253295ff6e
91 files changed
+18268
-7856
No files matched your search
@@ -1,4 +1,5 @@
|
||||
[deps]
|
||||
AbbreviatedStackTraces = "ac637c84-cc71-43bf-9c33-c1b4316be3d4"
|
||||
CalculusWithJulia = "a2e0e22d-7d4c-5312-9169-8b992201a882"
|
||||
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
||||
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
|
||||
|
||||
@@ -12,22 +12,16 @@ nothing
|
||||
Let us consider a basic calculator with buttons to add, subtract, multiply, divide, and take square roots. Using such a simple thing is certainly familiar for any reader of these notes. Indeed, a familiarity with a *graphing* calculator is expected. `Julia` makes these familiar tasks just as easy, offering numerous conveniences along the way. In this section we describe how.
|
||||
|
||||
|
||||
The following image is the calculator that Google presents upon searching for "calculator."
|
||||
::: {#fig-google-calculator-screenshot-1}
|
||||

|
||||
|
||||
Screenshot of a calculator provided by the Google search engine
|
||||
:::
|
||||
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
#
|
||||
imgfile = "figures/calculator.png"
|
||||
caption = "Screenshot of a calculator provided by the Google search engine."
|
||||
# ImageFile(:precalc, imgfile, caption)
|
||||
nothing
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
This calculator should have a familiar appearance with a keypad of numbers, a set of buttons for arithmetic operations, a set of buttons for some common mathematical functions, a degree/radian switch, and buttons for interacting with the calculator: `Ans`, `AC` (also `CE`), and `=`.
|
||||
@fig-google-calculator-screenshot-1 is a screenshot of a calculator that Google presented upon searching for "calculator". This calculator should have a familiar appearance with a keypad of numbers, a set of buttons for arithmetic operations, a set of buttons for some common mathematical functions, a degree/radian switch, and buttons for interacting with the calculator: `Ans`, `AC` (also `CE`), and `=`.
|
||||
|
||||
|
||||
The goal here is to see the counterparts within `Julia` to these features.
|
||||
@@ -60,17 +54,16 @@ Performing a simple computation on the calculator typically involves hitting but
|
||||
1 + 2
|
||||
```
|
||||
|
||||
Sending an expression to `Julia`'s interpreter - the equivalent of pressing the "`=`" key on a calculator - is done at the command line by pressing the `Enter` or `Return` key, and in `Pluto`, also using the "play" icon, or the keyboard shortcut `Shift-Enter`. If the current expression is complete, then `Julia` evaluates it and shows any output. If the expression is not complete, `Julia`'s response depends on how it is being called. Within `Pluto`, a message about "`premature end of input`" is given. If the expression raises an error, this will be noted.
|
||||
Sending an expression to `Julia`'s interpreter---the equivalent of pressing the "`=`" key on a calculator---is done at the command line by pressing the `Enter` or `Return` key. If the current expression is complete, then `Julia` evaluates it and shows any output. If the expression is not complete, `Julia`'s response at the command line pauses for more input. Other interfaces may have variations of the above.
|
||||
|
||||
|
||||
The basic arithmetic operations on a calculator are "+", "-", "×", "÷", and "$xʸ$". These have parallels in `Julia` through the *binary* operators: `+`, `-`, `*`, `/`, and `^`:
|
||||
The basic arithmetic operations on a calculator are +, -, ×, ÷ and xʸ. These have parallels in `Julia` through the *binary* operators: `+`, `-`, `*`, `/`, and `^`:
|
||||
|
||||
|
||||
```{julia}
|
||||
1 + 2, 2 - 3, 3 * 4, 4 / 5, 5 ^ 6
|
||||
```
|
||||
|
||||
On some calculators, there is a distinction between minus signs - the binary minus sign and the unary minus sign to create values such as $-1$.
|
||||
On some calculators, there is a distinction between minus signs---the binary minus sign and the unary minus sign to create values such as $-1$.
|
||||
|
||||
|
||||
In `Julia`, the same symbol, "`-`", is used for each:
|
||||
@@ -89,7 +82,7 @@ An expression like $6 - -3$, subtracting minus three from six, must be handled w
|
||||
|
||||
(If no space is included, the value "`--`" is parsed like a different, invalid, operation.)
|
||||
|
||||
:::{.callout-warning}
|
||||
::: {.callout-warning}
|
||||
## Warning
|
||||
`Julia` only uses one symbol for minus, but web pages may not! Copying
|
||||
and pasting an expression with a minus sign can lead to hard to
|
||||
@@ -101,13 +94,10 @@ the typeset math (e.g., $1 - \pi$) than for the code within cells
|
||||
:::
|
||||
|
||||
|
||||
### Examples
|
||||
##### Example: Celsius and Fahrenheit
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
|
||||
For everyday temperatures, the conversion from Celsius to Fahrenheit ($9/5 C + 32$) is well approximated by simply doubling and adding $30$. Compare these values for an average room temperature, $C=20$, and for a relatively chilly day, $C=5$:
|
||||
For everyday temperatures, the conversion from Celsius to Fahrenheit ($9/5\cdot C + 32$) is well approximated by simply doubling and adding $30$. Compare these values for an average room temperature, $C=20$, and for a relatively chilly day, $C=5$:
|
||||
|
||||
|
||||
For $C=20$:
|
||||
@@ -188,7 +178,7 @@ A right triangle has sides $a=11$ and $b=12$. Find the length of the hypotenus
|
||||
11^2 + 12^2
|
||||
```
|
||||
|
||||
##### Example
|
||||
##### Example: How many ants?
|
||||
|
||||
An overview of a research paper published in [theconversation.com](https://theconversation.com/earth-harbours-20-000-000-000-000-000-ants-and-they-weigh-more-than-wild-birds-and-mammals-combined-190831) reviews six authors' work on estimating the number of ants currently on earth. This was covered in an
|
||||
article in the [Washington Post](https://www.washingtonpost.com/climate-environment/2022/09/19/ants-population-20-quadrillion/).
|
||||
@@ -244,7 +234,7 @@ With the Google Calculator, typing `1 + 2 x 3 =` will give the value $7$, but *i
|
||||
In `Julia`, the entire expression is typed in before being evaluated, so the usual conventions of mathematics related to the order of operations may be used. These are colloquially summarized by the acronym [PEMDAS](http://en.wikipedia.org/wiki/Order_of_operations).
|
||||
|
||||
|
||||
> **PEMDAS**. This acronym stands for Parentheses, Exponents, Multiplication, Division, Addition, Subtraction. The order indicates which operation has higher precedence, or should happen first. This isn't exactly the case, as "M" and "D" have the same precedence, as do "A" and "S". In the case of two operations with equal precedence, *associativity* is used to decide which to do. For the operations `-`, `/` the associativity is left to right, as in the left one is done first, then the right. However, `^` has right associativity, so `4^3^2` is `4^(3^2)` and not `(4^3)^2` (Be warned that some calculators - and spread sheets, such as Excel - will treat this expression with left associativity). But, `+` and `*` don't have associativity, so `1+2+3` can be `(1+2)+3` or `1+(2+3)`.
|
||||
> **PEMDAS**. This acronym stands for Parentheses, Exponents, Multiplication, Division, Addition, Subtraction. The order indicates which operation has higher precedence, or should happen first. This isn't exactly the case, as "M" and "D" have the same precedence, as do "A" and "S". In the case of two operations with equal precedence, *associativity* is used to decide which to do. For the operations `-`, `/` the associativity is left to right, as in the left one is done first, then the right. However, `^` has right associativity, so `4^3^2` is `4^(3^2)` and not `(4^3)^2` (Be warned that some calculators---and spread sheets, such as Excel---will treat this expression with left associativity). The operators `+` and `*` are called associative, which means the order doesn't matter, so `1+2+3` can be `(1+2)+3` or `1+(2+3)`.^[Well, mathematically, floating point math has well known cases for `+` where associativity does not hold, such as `(0.1 + 0.2) + 0.3` not begin exactly equal to `0.1 + (0.2 + 0.3)`]
|
||||
|
||||
|
||||
|
||||
@@ -269,8 +259,6 @@ If different parentheses are used, the answer will likely be different. For exam
|
||||
(1 + ((2 - 3) * 4)) / (5 ^ 6)
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
@@ -284,6 +272,20 @@ The percentage error in $x$ if $y$ is the correct value is $(x-y)/y \cdot 100$.
|
||||
|
||||
##### Example
|
||||
|
||||
The *percentage decrease* of an item is $(o-n)/o \cdot 100$ with $o$ being the original price and $n$ being the new one. If a drug that cost 60 dollars is not 55 dollars what is the percentage decrease?
|
||||
|
||||
```{julia}
|
||||
(60 - 50) / 60 * 100
|
||||
```
|
||||
|
||||
The "different way of doing math" would compute this as $(o-n)/n \cdot 100$. With that unusual style, this nonmathematical value is:
|
||||
|
||||
```{julia}
|
||||
(60 - 50)/50 * 100
|
||||
```
|
||||
|
||||
##### Example
|
||||
|
||||
|
||||
The marginal cost of producing one unit can be computed by finding the cost for $n+1$ units and subtracting the cost for $n$ units. If the cost of $n$ units is $n^2 + 10$, find the marginal cost when $n=100$.
|
||||
|
||||
@@ -312,7 +314,7 @@ The slope of the line through two points is $m=(y_1 - y_0) / (x_1 - x_0)$. For t
|
||||
(4 - 2) / (3 - 1)
|
||||
```
|
||||
|
||||
### Two ways to write division - and they are not the same
|
||||
### Two ways to write division
|
||||
|
||||
|
||||
The expression $a + b / c + d$ is equivalent to $a + (b/c) + d$ due to the order of operations. It will generally have a different answer than $(a + b) / (c + d)$.
|
||||
@@ -325,7 +327,7 @@ $$
|
||||
\frac{1 + 2}{3 + 4}?
|
||||
$$
|
||||
|
||||
It would have to be computed through $(1 + 2) / (3 + 4)$. This is because unlike `/`, the implied order of operation in the mathematical notation with the *horizontal division symbol* (the [vinculum](http://tinyurl.com/y9tj6udl)) is to compute the top and the bottom and then divide. That is, the vincula is a grouping notation like parentheses, only implicitly so. Thus the above expression really represents the more verbose:
|
||||
It would have to be computed through $(1 + 2) / (3 + 4)$. This is because unlike `/`, the implied order of operations in the mathematical notation with the *horizontal division symbol* (the [vinculum](http://tinyurl.com/y9tj6udl)) is to compute the top and the bottom and then divide. That is, the vincula is a grouping notation like parentheses, only implicitly so. Thus the above expression really represents the more verbose:
|
||||
|
||||
|
||||
$$
|
||||
@@ -346,7 +348,7 @@ To emphasize, this is not the same as the value without the parentheses:
|
||||
1 + 2 / 3 + 4
|
||||
```
|
||||
|
||||
:::{.callout-warning}
|
||||
::: {.callout-warning}
|
||||
## Warning
|
||||
The vinculum also indicates grouping when used with the square root (the top bar), and complex conjugation. That usage is often clear enough, but the usage of the vinculum in division often leads to confusion. The example above is one where the parentheses are often, erroneously, omitted. However, more confusion can arise when there is more than one vincula. An expression such as $a/b/c$ written inline has no confusion, it is: $(a/b) / c$ as left association is used; but when written with a pair of vincula there is often the typographical convention of a slightly longer vincula to indicate which is to be considered first. In the absence of that, then top to bottom association is often implied.
|
||||
|
||||
@@ -406,7 +408,7 @@ In most cases. There are occasional (basically rare) spots where using `pi` by i
|
||||
### Numeric literals
|
||||
|
||||
|
||||
For some special cases, Julia parses *multiplication* without a multiplication symbol. One case is when the value on the left is a number, as in `2pi`, which has an equivalent value to `2*pi`. *However* the two are not equivalent, in that multiplication with *numeric literals* does not have the same precedence as regular multiplication - it is higher. This has practical importance when used in division or powers. For instance, these two expressions are **not** the same:
|
||||
For some special cases, Julia parses *multiplication* without a multiplication symbol. One case is when the value on the left is a number, as in `2pi`, which has an equivalent value to `2*pi`. *However* the two are not equivalent, in that multiplication with *numeric literals* does not have the same precedence as regular multiplication---**it is higher**. This has practical importance when used in division or powers. For instance, these two expressions are **not** the same:
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -439,44 +441,28 @@ This follows usual mathematical convention, but is a source of potential confusi
|
||||
On the Google calculator, the square root button has a single purpose: for the current value find a square root if possible, and if not signal an error (such as what happens if the value is negative). For more general powers, the $x^y$ key can be used.
|
||||
|
||||
|
||||
In `Julia`, functions are used to perform the actions that a specialized button may do on the calculator. `Julia` provides many standard mathematical functions - more than there could be buttons on a calculator - and allows the user to easily define their own functions. For example, `Julia` provides the same set of functions as on Google's calculator, though with different names. For logarithms, $\ln$ becomes `log` and $\log$ is `log10` (computer programs almost exclusively reserve `log` for the natural log); for factorials, $x!$, there is `factorial`; for powers $\sqrt{...}$ becomes `sqrt`, $EXP$ becomes `exp`, and $x^y$ is computed with the infix operator `^`. For the trigonometric functions, the basic names are similar: `sin`, `cos`, `tan`. These expect radians. For angles in degrees, the convenience functions `sind`, `cosd`, and `tand` are provided. On the calculator, inverse functions like $\sin^{-1}(x)$ are done by combining $Inv$ with $\sin$. With `Julia`, the function name is `asin`, an abbreviation for "arcsine." (Which is a good thing, as the notation using a power of $-1$ is often a source of confusion and is not supported by `Julia` without work.) Similarly, there are `asind`, `acos`, `acosd`, `atan`, and `atand` functions available to the `Julia` user.
|
||||
In `Julia`, functions are used to perform the actions that a specialized button may do on the calculator. `Julia` provides many standard mathematical functions---more than there could be buttons on a calculator---and allows the user to easily define their own functions. For example, `Julia` provides the same set of functions as on Google's calculator, though with different names. For logarithms, $\ln$ becomes `log` and $\log$ is `log10` (computer programs almost exclusively reserve `log` for the natural log); for factorials, $x!$, there is `factorial`; for powers $\sqrt{...}$ becomes `sqrt`, $EXP$ becomes `exp`, and $x^y$ is computed with the infix operator `^`. For the trigonometric functions, the basic names are similar: `sin`, `cos`, `tan`. These expect radians. For angles in degrees, the convenience functions `sind`, `cosd`, and `tand` are provided. On the calculator, inverse functions like $\sin^{-1}(x)$ are done by combining $Inv$ with $\sin$. With `Julia`, the function name is `asin`, an abbreviation for "arcsine." (Which is a good thing, as the notation using a power of $-1$ is often a source of confusion.) Similarly, there are `asind`, `acos`, `acosd`, `atan`, and `atand` functions available to the `Julia` user.
|
||||
|
||||
@tbl-calculator-and-julia-counterparts shows the counterparts between a calculator's dedicated buttons and `Julia` functions.
|
||||
|
||||
The following table summarizes the above:
|
||||
::: {#tbl-calculator-and-julia-counterparts .striped .hover}
|
||||
|
||||
| Calculator | Julia |
|
||||
| ------------------------------------:| ----------------------------------------------:|
|
||||
| $+$, $-$, $\times$, $\div$ | `+`, `-`, `*`, `/` |
|
||||
| $x^y$ | `^` |
|
||||
| $\sqrt{...}$, $\sqrt[3]{...}$ | `sqrt`, `cbrt` |
|
||||
| $EXP$, $e^x$ | `exp` |
|
||||
| $\ln$, $\log$ | `log`, `log10` |
|
||||
| $\sin, \cos, \tan, \sec, \csc, \cot$ | `sin`, `cos`, `tan`, `sec`, `csc`, `cot` |
|
||||
| In degrees, not radians | `sind`, `cosd`, `tand`, `secd`, `cscd`, `cotd` |
|
||||
| $\sin^{-1}, \cos^{-1}, \tan^{-1}$ | `asin`, `acos`, `atan` |
|
||||
| $n!$ | `factorial` |
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
using DataFrames
|
||||
calc = [
|
||||
L" $+$, $-$, $\times$, $\div$",
|
||||
L"x^y",
|
||||
L"\sqrt{...}, \sqrt[3]{...}",
|
||||
L"e^x",
|
||||
L" $\ln$, $\log$",
|
||||
L"\sin, \cos, \tan, \sec, \csc, \cot",
|
||||
"In degrees, not radians",
|
||||
L"\sin^{-1}, \cos^{-1}, \tan^{-1}",
|
||||
L"n!",
|
||||
]
|
||||
: Table matching up common buttons on a scientific calculator with `Julia` functions
|
||||
:::
|
||||
|
||||
|
||||
julia = [
|
||||
"`+`, `-`, `*`, `/`",
|
||||
"`^`",
|
||||
"`sqrt`, `cbrt`",
|
||||
"`exp`",
|
||||
"`log`, `log10`",
|
||||
"`sin`, `cos`, `tan`, `sec`, `csc`, `cot`",
|
||||
"`sind`, `cosd`, `tand`, `secd`, `cscd`, `cotd`",
|
||||
"`asin`, `acos`, `atan`",
|
||||
"`factorial`"
|
||||
]
|
||||
|
||||
d = DataFrame(Calculator=calc, Julia=julia)
|
||||
Table(d)
|
||||
```
|
||||
|
||||
Using a function is very straightforward. A function is called using parentheses, in a manner visually similar to how a function is called mathematically. So if we consider the `sqrt` function, we have:
|
||||
|
||||
|
||||
@@ -498,7 +484,7 @@ exp(2), log(10), sqrt(100), 10^(1/2)
|
||||
|
||||
:::{.callout-note}
|
||||
## Note
|
||||
Parentheses have many roles. We've just seen that parentheses may be used for grouping, and now we see they are used to indicate a function is being called. These are familiar from their parallel usage in traditional math notation. In `Julia`, a third usage is common, the making of a "tuple," or a container of different objects, for example `(1, sqrt(2), pi)`. In these notes, the output of multiple commands separated by commas is a printed tuple.
|
||||
Parentheses have many roles. We've just seen that parentheses may be used for grouping, and now we see they are used to indicate a function is being called. These are familiar from their parallel usage in traditional math notation. In `Julia`, a third usage is common, the making of a "tuple," or a container of different objects, for example `(1, sqrt(2), pi)`. In these notes, the output of multiple commands separated by commas is a printed tuple, as with the previous output.
|
||||
|
||||
:::
|
||||
|
||||
@@ -518,8 +504,6 @@ log(e), log(2, e), log(10, e), log(e, 2)
|
||||
In `Julia`, the "generic" function `log` not only has different implementations for different types of arguments (real or complex), but also a different implementation depending on the number of arguments.
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
@@ -567,7 +551,10 @@ The formula to compute the resistance of two resistors in parallel is given by:
|
||||
Not all computations on a calculator are valid. For example, the Google calculator will display `Error` as the output of $0/0$ or $\sqrt{-1}$. These are also errors mathematically, though the second is not if the complex numbers are considered.
|
||||
|
||||
|
||||
In `Julia`, there is a richer set of error types. The value `0/0` will in fact not be an error, but rather a value `NaN`. This is a special floating point value indicating "not a number" and is the result for various operations. The output of $\sqrt{-1}$ (computed via `sqrt(-1)`) will indicate a domain error:
|
||||
In `Julia`, there is a richer set of error types. The value `0/0` will in fact not be an error, but rather a value `NaN`. This is a special floating point value indicating "not a number" and is the result for various operations.
|
||||
|
||||
|
||||
The output of $\sqrt{-1}$ (computed via `sqrt(-1)`) will indicate a domain error:
|
||||
|
||||
```{julia}
|
||||
#| error: true
|
||||
@@ -592,7 +579,7 @@ On a machine with $64$-bit integers, the first of these two values is correct, t
|
||||
|
||||
Wrong is in quotes, as though they are mathematically incorrect, computationally they are correct. The last two are due to overflow. The cost of checking is considered too high, so no error is thrown and the values represent what happens at the machine level.
|
||||
|
||||
The user is expected to have a sense that they need to be careful when their values are quite large. But the better recommendation is that the user use floating point numbers, which as easy as typing `2.0^63`. Though not always exact, floating point values can represent a much bigger range values and are exact for a reasonably wide range of integer values.
|
||||
The user is expected to have a sense that they need to be careful when their values are quite large. But the better recommendation is that the user use floating point numbers, which is as easy as typing `2.0^63`. Though not always exact, floating point values can represent a much bigger range values and are exact for a reasonably wide range of integer values.
|
||||
|
||||
|
||||
::: {.callout-note}
|
||||
@@ -600,7 +587,7 @@ The user is expected to have a sense that they need to be careful when their val
|
||||
|
||||
We can see in the following, using the smaller 8-bit type, what goes on internally with successive powers of `2`: the bit pattern is found by shifting the previous one over to the left, consistent with what happens at the bit level when multiplying by `2`:
|
||||
|
||||
```
|
||||
```{julia}
|
||||
[bitstring(Int8(2)^i) for i in 1:8]
|
||||
```
|
||||
|
||||
@@ -1131,7 +1118,7 @@ val = 8/2*(2+2)
|
||||
numericq(val)
|
||||
```
|
||||
|
||||
Does this expression return the *correct* answer using proper order of operations?
|
||||
Does this expression return the *correct* answer using the proper order of operations?
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -1189,8 +1176,8 @@ $$
|
||||
Attempting to compute this, we have:
|
||||
|
||||
```{julia}
|
||||
c = 299_792_458; # the speed of light
|
||||
G = 6.67430e-11; # Gravitational constant
|
||||
c = 299_792_458; # the speed of light
|
||||
G = 6.67430e-11; # Gravitational constant
|
||||
h = 6.62607015e-34; # Planck's constant
|
||||
h_bar = h / (2*pi);
|
||||
planck_length = sqrt(h_bar * G / c^3)
|
||||
@@ -1209,3 +1196,40 @@ Yes, this is computed incorrectly. The value `c^3` *overflows* as the actual val
|
||||
"""
|
||||
buttonq(choices, 3; explanation=explanation)
|
||||
```
|
||||
|
||||
## Appendix
|
||||
|
||||
The order of operations for common operators are summarized in `Julia`'s [manual](https://docs.julialang.org/en/v1/manual/mathematical-operations/) in the "Operator Precedence and Associativity" section. @tbl-precedence-associativity is an edited version of one presented there.
|
||||
|
||||
::: {#tbl-precedence-associativity}
|
||||
|
||||
| Category | Operators | Associativity |
|
||||
|:---------------|:--------------------------------|:---------------|
|
||||
|Syntax | `.` followed by `::` | Left |
|
||||
|*Exponentiation*| `^` | Right |
|
||||
|Unary | `+`, `-` `!`, `~`, `√` | Right |
|
||||
|Bit shifts | `<<`, `>>`, `>>>` | Left |
|
||||
|Fractions | `//` | Left |
|
||||
|*Multiplication*| `*`, `/`, `%`, `÷`, `&` | Left |
|
||||
|*Addition* | `+`,`-`, `|` | Left |
|
||||
|Syntax | `:`, `..` | Left |
|
||||
|Syntax | `|>` | Right |
|
||||
|Comparisons | `<|`, `>`, `<`, `>=`, `<=`, `==`, `===`, `!=`, `!--` | Non-associative |
|
||||
|Control flow | `&&` followed by `||` followed by `?` | Right |
|
||||
|Pair | `=>` | Right |
|
||||
|Assignment | `=` `+-`, `-=`, `*=`, `/= | Right |
|
||||
|
||||
: Table highlighting the precedence and associativity of different types of operators. Adapted from the Julia manual.
|
||||
:::
|
||||
|
||||
|
||||
Parentheses force evaluation, so aren't listed in the table. Otherwise we highlighted `⋅EM⋅A⋅` with italics. The `DS` are in the list of operators.
|
||||
|
||||
The low precedence of the comparison, pair, and assignment operators ensure the left and right hand sides are parsed and evaluated before these binary operations, reducing the number of necessary parentheses.
|
||||
|
||||
The low precedence of `?` is helpful. This symbol is used in the `ternary` operation and this low precedence requires fewer parentheses for most uses.
|
||||
|
||||
The high precedence of the **unary** operations allows natural syntax for powers, where `2^-3` evaluates `-3` and then takes the power. However, note `2^1/2` will first find `2^1` and then divide by `2`.
|
||||
|
||||
|
||||
The code that defines the order is in [Julia Syntax](https://github.com/JuliaLang/JuliaSyntax.jl/blob/main/src/julia/kinds.jl). A perusal will show that there are **numerous** unicode operations not listed in the @tbl-precedence-associativity above.
|
||||
@@ -18,7 +18,7 @@ plotly()
|
||||
nothing
|
||||
```
|
||||
|
||||
## Boolean values
|
||||
## Boolean values, comparison operators
|
||||
|
||||
|
||||
In mathematics it is common to test if an expression is true or false. For example, is the point $(1,2)$ inside the disc $x^2 + y^2 \leq 1$? We would check this by substituting $1$ for $x$ and $2$ for $y$, evaluating both sides of the inequality and then assessing if the relationship is true or false. In this case, we end up with a comparison of $5 \leq 1$, which we of course know is false.
|
||||
@@ -36,12 +36,12 @@ x^2 + y^2 <= 1
|
||||
The response is `false`, as expected. `Julia` provides [Boolean](http://en.wikipedia.org/wiki/Boolean_data_type) values `true` and `false` for such questions. The same process is followed as was described mathematically.
|
||||
|
||||
|
||||
The set of numeric comparisons is nearly the same as the mathematical counterparts: `<`, `<=`, `==`, `>=`, `>`. The syntax for less than or equal can also be represented with the Unicode `≤` (generated by `\le[tab]`). Similarly, for greater than or equal, there is `\ge[tab]`.
|
||||
The set of comparison operators is nearly the same as the mathematical counterparts: `<`, `<=`, `==`, `>=`, `>`. The syntax for less than or equal can also be represented with the Unicode `≤` (generated by `\le[tab]`). Similarly, for greater than or equal, there is `\ge[tab]`.
|
||||
|
||||
|
||||
:::{.callout-warning}
|
||||
## Warning
|
||||
The use of `==` is necessary, as `=` is used for assignment and mutation.
|
||||
The use of `==` is necessary, as `=` is used for assignment and mutation and `===` is used for *identicalness* from a hardware standpoint.
|
||||
|
||||
:::
|
||||
|
||||
@@ -54,6 +54,13 @@ The `!` operator takes a boolean value and negates it. It uses prefix notation:
|
||||
|
||||
For convenience, `a != b` can be used in place of `!(a == b)`.
|
||||
|
||||
----
|
||||
|
||||
The function `isapprox` has a unicode counterpart ` ≈` (typed through `\approx[tab]`) that allows numeric comparisons up to a tolerance. This is needed when comparing floating point values. For example, due to the inability to exactly represent the numbers in floating point the first of these comparisons is false:
|
||||
|
||||
```{julia}
|
||||
1/10 + 2/10 == 3/10, 1/10 + 2/10 ≈ 3/10
|
||||
```
|
||||
|
||||
## Algebra of inequalities
|
||||
|
||||
@@ -136,10 +143,13 @@ a < b, 1/a > 1/b
|
||||
In summary we investigated numerically that the following hold:
|
||||
|
||||
|
||||
* `a < b` if and only if `a + c < b + c` for all finite `a`, `b`, and `c`.
|
||||
* `a < b` if and only if `c*a < c*b` for all finite `a` and `b`, and finite, positive `c`.
|
||||
* `a < b` if and only if `-a > -b` for all finite `a` and `b`.
|
||||
* `a < b` if and only if `1/a > 1/b` for all finite, positive `a` and `b`.
|
||||
* `a < b` if and only if `a + c < b + c` for all finite `a`, `b`, and `c` in $[0,1]$.
|
||||
|
||||
* `a < b` if and only if `c*a < c*b` for all finite `a` and `b`, and finite, positive `c`, all in $[0,1]$.
|
||||
|
||||
* `a < b` if and only if `-a > -b` for all finite `a` and `b` in $[0,1]$.
|
||||
|
||||
* `a < b` if and only if `1/a > 1/b` for all finite, positive `a` and `b` in $[0,1]$.
|
||||
|
||||
|
||||
### Examples
|
||||
@@ -228,28 +238,42 @@ This is to be expected, but we could also have written:
|
||||
Read aloud this would be "minus $7$ is less than $x$ minus $5$ **and** $x$ minus $5$ is less than $7$".
|
||||
|
||||
|
||||
The "and" equations can be combined as above with a natural notation. However, an equation like $\lvert x - 5\rvert > 7$ would emphasize an **or** and be "$x$ minus $5$ less than minus $7$ **or** $x$ minus $5$ greater than $7$". Expressing this requires some new notation.
|
||||
::: {.callout-note}
|
||||
## Chaining expressions
|
||||
The above (`-7 < x - 5 < 7`)---where two logical comparisons are used, is referred to as chaining. It is parsed differently than might be expected and is not read in as `(-7 < x - 5) < 7` which would compare a boolean to `7`. Chaining is neither left- or right-associative.
|
||||
:::
|
||||
|
||||
|
||||
The *boolean shortcut operators* `&&` and `||` implement "and" and "or". (There are also *bitwise* boolean operators `&` and `|`, but we only describe the former.)
|
||||
## Short-circuit operators
|
||||
|
||||
The "and" equations can be combined as above with a natural notation through chaining. However, an equation like $\lvert x - 5\rvert > 7$ would emphasize an **or** and be "$x$ minus $5$ less than minus $7$ **or** $x$ minus $5$ greater than $7$". Expressing this requires some new notation.
|
||||
|
||||
|
||||
The *boolean short-circuiting operators* `&&` and `||` implement "and" and "or".
|
||||
|
||||
Thus we could write $-7 < x-5 < 7$ as
|
||||
|
||||
|
||||
```{julia}
|
||||
(-7 < x - 5) && (x - 5 < 7)
|
||||
(-7 < x - 5) && (x - 5 < 7) # and
|
||||
```
|
||||
|
||||
and could write $\lvert x-5\rvert > 7$ as
|
||||
|
||||
|
||||
```{julia}
|
||||
(x - 5 < -7) || (x - 5 > 7)
|
||||
(x - 5 < -7) || (x - 5 > 7) # or
|
||||
```
|
||||
|
||||
(The first expression is false for $x=18$ and the second expression true, so the "or"ed result is `true` and the "and" result is `false`.)
|
||||
The first expression is false for $x=18$ and the second expression true, so the "and" result is `false` and the "or"ed result is `true`.
|
||||
|
||||
These operators are called *short-circuiting* operators.
|
||||
|
||||
The operation `x && y` evaluates `x` and if true returns the evaluated value of `y`. If `x` is false, `y` is not evaluated (it is short-circuited).
|
||||
|
||||
The operation `x || y` evaluates `x` and if true return `true` and if false the value of `y` is evaluated and returned.
|
||||
|
||||
Both are idiomatically used for control flow (if `x` then `y` or if *not* `x` then `y` statements).
|
||||
|
||||
##### Example
|
||||
|
||||
@@ -262,6 +286,13 @@ A,B = true, false ## also true, true; false, true; and false, false
|
||||
!(A && B) == !A || !B
|
||||
```
|
||||
|
||||
::: {.callout-note}
|
||||
## Bitwise `&` and `|`
|
||||
|
||||
Both the short-circuiting operators and the bitwise operators can evaluate truth tables---where Boolean values are compared---but the short-circuiting values *don't* need the second argument to evaluate to a Boolean. The bitwise operators also have a different precedence; the same as `*` and `/` and not lower precedence than the arithmetic operations.
|
||||
:::
|
||||
|
||||
|
||||
## Precedence
|
||||
|
||||
|
||||
@@ -281,7 +312,6 @@ x - 5 < -7 || x - 5 > 7
|
||||
|
||||
So no, they were not in this case.
|
||||
|
||||
|
||||
An operator (such as `<`, `>`, `||` above) has an associated associativity and precedence. The associativity is whether an expression like `a - b - c` is `(a-b) - c` or `a - (b-c)`. The former being left associative, the latter right. Of issue here is *precedence*, as in with two or more different operations, which happens first, second, $\dots$.
|
||||
|
||||
|
||||
@@ -295,7 +325,7 @@ The table in the manual on [operator precedence and associativity](https://docs.
|
||||
(This is different than the precedence of the bitwise boolean operators, which have `&` with "Multiplication" and `|` with "Addition", so `x-5 < 7 | x - 5 > 7` would need parentheses.)
|
||||
|
||||
|
||||
A thorough understanding of the precedence rules can help eliminate unnecessary parentheses, but in most cases it is easier just to put them in.
|
||||
A thorough understanding of the precedence rules can help eliminate unnecessary parentheses, but if in doubt, just put them in.
|
||||
|
||||
|
||||
## Arithmetic with
|
||||
@@ -308,7 +338,7 @@ For convenience, basic arithmetic can be performed with Boolean values, `false`
|
||||
true + true + false, false * 1000
|
||||
```
|
||||
|
||||
The first example shows a common means used to count the number of `true` values in a collection of Boolean values - just add them.
|
||||
The first example shows a common means used to count the number of `true` values in a collection of Boolean values---just add them.
|
||||
|
||||
|
||||
This can be cleverly exploited. For example, the following expression returns `x` when it is positive and $0$ otherwise:
|
||||
@@ -328,7 +358,15 @@ This expression returns `x` if it is between $-10$ and $10$ and otherwise $-10$
|
||||
(x < -10)*(-10) + (x >= -10)*(x < 10) * x + (x>=10)*10
|
||||
```
|
||||
|
||||
The `clamp(x, a, b)` performs this task more generally, and is used as in `clamp(x, -10, 10)`.
|
||||
The `clamp(x, a, b)` performs this task more generally.
|
||||
|
||||
### Example
|
||||
|
||||
The value of `im`, representing $i$ the imaginary number with $i^2 = -1$ is internally modeled using a Boolean `false` for the real part of a complex number and a value `true` for the imaginary part.
|
||||
|
||||
```{julia}
|
||||
dump(im)
|
||||
```
|
||||
|
||||
|
||||
## Questions
|
||||
@@ -398,6 +436,27 @@ answ = 3
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
Compute the following:
|
||||
|
||||
```{julia}
|
||||
#| eval: false
|
||||
u = typemax(1)
|
||||
u < u + 1
|
||||
```
|
||||
|
||||
Is the output as expected?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
explanation = """
|
||||
No, for integer numbers in math, it is always the case that `u < u + 1`, but for this particular `u` there is *no larger* integer, so `u+1` "wraps around" to give a negative number which on comparison is less than `u`
|
||||
"""
|
||||
buttonq(["Yes", "No"], 2; explanation)
|
||||
```
|
||||
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,685 @@
|
||||
# Number systems
|
||||
|
||||
|
||||
{{< include ../_common_code.qmd >}}
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
#| results: "hidden"
|
||||
using CalculusWithJulia
|
||||
|
||||
nothing
|
||||
```
|
||||
|
||||
In mathematics, there are many different number systems in common use. For example by the end of pre-calculus, all of the following have been introduced:
|
||||
|
||||
|
||||
* The integers, $\{\dots, -3, -2, -1, 0, 1, 2, 3, \dots\}$;
|
||||
* The rational numbers, $\{p/q: p, q \text{ are integers}, q \neq 0\}$;
|
||||
* The real numbers, $\{x: -\infty < x < \infty\}$;
|
||||
* The complex numbers, $\{a + bi: a,b \text{ are real numbers and } i^2=-1\}$.
|
||||
|
||||
|
||||
On top of these, we have special subsets, such as the natural numbers $\{1, 2, \dots\}$ (sometimes including $0$), the even numbers, the odd numbers, the positive numbers, the non-negative numbers, etc.
|
||||
|
||||
|
||||
Mathematically, these number systems are naturally nested within each other as integers are rational numbers which are real numbers, which can be viewed as part of the complex numbers.
|
||||
|
||||
|
||||
Calculators typically have just one type of number - floating point values. These model the real numbers. `Julia`, on the other hand, has a rich type system, and within that has several different number types. There are types that model each of the four main systems above, and within each type, specializations for how these values are stored.
|
||||
|
||||
|
||||
Most of the details will not be of interest to all, and will be described later.
|
||||
|
||||
|
||||
For now, let's consider the number $1$. It can be viewed as either an integer, rational, real, or complex number. To construct "$1$" in each type within `Julia` we have these different styles:
|
||||
|
||||
|
||||
```{julia}
|
||||
1, 1.0, 1//1, 1 + 0im
|
||||
```
|
||||
|
||||
The basic number types in `Julia` are `Int`, `Float64`, `Rational` and `Complex`, though in fact there are many more, and the last two aren't even *concrete* types. This distinction is important, as the type of number dictates how it will be stored and how precisely the stored value can be expected to be to the mathematical value it models.
|
||||
|
||||
|
||||
Though there are explicit constructors for these types, these notes avoid them unless necessary, as `Julia`'s parser can distinguish these types through an easy to understand syntax:
|
||||
|
||||
|
||||
* integers have no decimal point;
|
||||
* floating point numbers have a decimal point (or are in scientific notation);
|
||||
* rationals are constructed from integers using the double division operator, `//`; and
|
||||
* complex numbers are formed by including a term with the imaginary unit, `im`.
|
||||
|
||||
|
||||
:::{.callout-note}
|
||||
## Warning
|
||||
Heads up, the difference between `1` and `1.0` is subtle. Even more so, as `1.` will parse as `1.0`. This means some expressions, such as `2.*3`, are ambiguous, as the `.` might be part of the `2` (as in `2. * 3`) or the operation `*` (as in `2 .* 3`).
|
||||
|
||||
:::
|
||||
|
||||
Similarly, each type is printed slightly differently.
|
||||
|
||||
|
||||
The key distinction is between integers and floating points. While floating point values include integers, and so can be used exclusively on the calculator, the difference is that an integer is guaranteed to be an exact value, whereas a floating point value, while often an exact representation of a number is also often just an *approximate* value. This can be an advantage – floating point values can model a much wider range of numbers.
|
||||
|
||||
In nearly all cases the differences are not noticeable. Take for instance this simple calculation involving mixed types.
|
||||
|
||||
|
||||
```{julia}
|
||||
1 + 1.25 + 3//2
|
||||
```
|
||||
|
||||
The sum of an integer, a floating point number and rational number returns a floating point number without a complaint.
|
||||
|
||||
|
||||
This is because behind the scenes, `Julia` will often "promote" a type to match, so for example to compute `1 + 1.25` the integer `1` will be promoted to a floating point value and the two values are then added. Similarly, with `2.25 + 3//2`, where the fraction is promoted to the floating point value `1.5` and addition is carried out.
|
||||
|
||||
|
||||
As floating point numbers may be approximations, some values are not quite what they would be mathematically:
|
||||
|
||||
|
||||
```{julia}
|
||||
sqrt(2) * sqrt(2) - 2, sin(1pi), 1/10 + 1/5 - 3/10
|
||||
```
|
||||
|
||||
These values are *very* small numbers, but not exactly $0$, as they are mathematically.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
The only common issue is with powers. We saw this previously when discussing a distinction between `2^64` and `2.0^64`. `Julia` tries to keep a predictable output from the input types (not their values). Here are the two main cases that arise where this can cause unexpected results:
|
||||
|
||||
|
||||
* integer bases and integer exponents can *easily* overflow. Not only `m^n` is always an integer, it is always an integer with a fixed storage size computed from the sizes of `m` and `n`. So the powers can quickly get too big. This can be especially noticeable on older $32$-bit machines, where too big is $2^{32} = 4,294,967,296$. On $64$-bit machines, this limit is present but much bigger.
|
||||
|
||||
|
||||
Rather than give an error though, `Julia` gives seemingly arbitrary answers, as can be seen in this example on a $64$-bit machine:
|
||||
|
||||
|
||||
```{julia}
|
||||
2^62, 2^63
|
||||
```
|
||||
|
||||
(They aren't arbitrary, as explained previously.)
|
||||
|
||||
|
||||
This could be worked around, as it is with some programming languages, but it isn't, as it would slow down this basic computation. So, it is up to the user to be aware of cases where their integer values can grow to big. The suggestion is to use floating point numbers in this domain, as they have more room, at the cost of sometimes being approximate values for fairly large values.
|
||||
|
||||
|
||||
* the `sqrt` function will give a domain error for negative values:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| error: true
|
||||
sqrt(-1.0)
|
||||
```
|
||||
|
||||
This is because for real-valued inputs `Julia` expects to return a real-valued output. Of course, this is true in mathematics until the complex numbers are introduced. Similarly in `Julia` - to take square roots of negative numbers, start with complex numbers:
|
||||
|
||||
|
||||
```{julia}
|
||||
sqrt(-1.0 + 0im)
|
||||
```
|
||||
|
||||
* At one point, `Julia` had an issue with a third type of power:
|
||||
|
||||
|
||||
integer bases and negative integer exponents. For example `2^(-1)`. This is now special cased, though only for numeric literals. If `z=-1`, `2^z` will throw a `DomainError`. Historically, the desire to keep a predictable type for the output (integer) led to defining this case as a domain error, but its usefulness led to special casing.
|
||||
|
||||
|
||||
## Additional details.
|
||||
|
||||
|
||||
What follows is only needed for those seeking more background.
|
||||
|
||||
|
||||
Julia has *abstract* number types `Integer`, `Real`, and `Number`. All four types described above are of type `Number`, but `Complex` is not of type `Real`.
|
||||
|
||||
|
||||
However, a specific value is an instance of a *concrete* type. A concrete type will also include information about how the value is stored. For example, the *integer* `1` could be stored using $64$ bits as a signed integers, or, should storage be a concern, as an $8$ bits signed or even unsigned integer, etc.. If storage isn't an issue, but exactness at all scales is, then it can be stored in a manner that allows for the storage to grow using "big" numbers.
|
||||
|
||||
|
||||
These distinctions can be seen in how `Julia` parses these three values:
|
||||
|
||||
|
||||
* `1234567890` will be a $64$-bit integer (on newer machines), `Int64`
|
||||
* `12345678901234567890` will be a $128$ bit integer, `Int128`
|
||||
* `1234567890123456789012345678901234567890` will be a big integer, `BigInt`
|
||||
|
||||
|
||||
Having abstract types allows programmers to write functions that will work over a wide range of input values that are similar, but have different implementation details.
|
||||
|
||||
|
||||
### Integers
|
||||
|
||||
|
||||
Integers are often used casually, as they come about from parsing. As with a calculator, floating point numbers *could* be used for integers, but in `Julia` - and other languages - it proves useful to have numbers known to have *exact* values. In `Julia` there are built-in number types for integers stored in $8$, $16$, $32$, $64$, and $128$ bits and `BigInt`s if the previous aren't large enough. ($8$ bits can hold $8$ binary values representing $1$ of $256=2^8$ possibilities, whereas the larger $128$ bit can hold one of $2^{128}$ possibilities.) Smaller values can be more efficiently used, and this is leveraged at the system level, but not a necessary distinction with calculus where the default size along with an occasional usage of `BigInt` suffice.
|
||||
|
||||
|
||||
### Floating point numbers
|
||||
|
||||
|
||||
[Floating point](http://en.wikipedia.org/wiki/Floating_point) numbers are a computational model for the real numbers. For floating point numbers, $64$ bits are used by default for both $32$- and $64$-bit systems, though other storage sizes can be requested. This gives a large range - but still finite - set of real numbers that can be represented. However, there are infinitely many real numbers just between $0$ and $1$, so there is no chance that all can be represented exactly on the computer with a floating point value. Floating point then is *necessarily* an approximation for all but a subset of the real numbers. Floating point values can be viewed in normalized [scientific notation](http://en.wikipedia.org/wiki/Scientific_notation) as $a\cdot 2^b$ where $a$ is the *significand* and $b$ is the *exponent*. Save for special values, the significand $a$ is normalized to satisfy $1 \leq \lvert a\rvert < 2$, the exponent can be taken to be an integer, possibly negative.
|
||||
|
||||
|
||||
As per IEEE Standard 754, the `Float64` type gives 52 bits to the precision (with an additional implied one), 11 bits to the exponent and the other bit is used to represent the sign. Positive, finite, floating point numbers have a range approximately between $10^{-308}$ and $10^{308}$, as 308 is about $\log_{10} 2^{1023}$. The numbers are not evenly spread out over this range, but, rather, are much more concentrated closer to $0$.
|
||||
|
||||
The use of 32-bit floating point values is common, as some widely used computer chips expect this. These values have a narrower range of possible values.
|
||||
|
||||
:::{.callout-warning}
|
||||
## More on floating point numbers
|
||||
You can discover more about the range of floating point values provided by calling a few different functions.
|
||||
|
||||
* `typemax(0.0)` gives the largest value for the type (`Inf` in this case).
|
||||
* `prevfloat(Inf)` gives the largest finite one, in general `prevfloat` is the next smallest floating point value.
|
||||
|
||||
* `nextfloat(-Inf)`, similarly, gives the smallest finite floating point value, and in general returns the next largest floating point value.
|
||||
* `nextfloat(0.0)` gives the closest positive value to 0.
|
||||
* `eps()` gives the distance to the next floating point number bigger than `1.0`. This is sometimes referred to as machine precision.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
#### Scientific notation
|
||||
|
||||
|
||||
Floating point numbers may print in a familiar manner:
|
||||
|
||||
|
||||
```{julia}
|
||||
x = 1.23
|
||||
```
|
||||
|
||||
or may be represented in scientific notation:
|
||||
|
||||
|
||||
```{julia}
|
||||
6.022 * 10.0^23
|
||||
```
|
||||
|
||||
The special coding `aeb` (or if the exponent is negative `ae-b`) is used to represent the number $a \cdot 10^b$ ($1 \leq a < 10$). This notation can be used directly to specify a floating point value:
|
||||
|
||||
|
||||
```{julia}
|
||||
avogadro = 6.022e23
|
||||
```
|
||||
|
||||
::: {.callout-note}
|
||||
## Not `e`
|
||||
Here `e` is decidedly *not* the Euler number, rather **syntax** to separate the exponent from the mantissa.
|
||||
:::
|
||||
|
||||
The first way of representing this number required using `10.0` and not `10` as the integer power will return an integer and even for 64-bit systems is only valid up to `10^18`. Using scientific notation avoids having to concentrate on such limitations.
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
|
||||
Floating point values in scientific notation will always be normalized. This is easy for the computer to do, but tedious to do by hand. Here we see:
|
||||
|
||||
|
||||
```{julia}
|
||||
4e30 * 3e40
|
||||
```
|
||||
|
||||
```{julia}
|
||||
3e40 / 4e30
|
||||
```
|
||||
|
||||
The power in the first is $71$, not $70 = 30+40$, as the product of $3$ and $4$ as $12$ or `1.2e^1`. (We also see the artifact of `1.2` not being exactly representable in floating point.)
|
||||
|
||||
|
||||
##### Example: 32-bit floating point
|
||||
|
||||
|
||||
In some uses, such as using a GPU, $32$-bit floating point (single precision) is also common. These values may be specified with an `f` in place of the `e` in scientific notation:
|
||||
|
||||
|
||||
```{julia}
|
||||
1.23f0
|
||||
```
|
||||
|
||||
As with the use of `e`, some exponent is needed after the `f`, even if it is `0`.
|
||||
|
||||
|
||||
#### Special values: Inf, -Inf, NaN
|
||||
|
||||
|
||||
The coding of floating point numbers also allows for the special values of `Inf`, `-Inf` to represent positive and negative infinity. As well, a special value `NaN` ("not a number") is used to represent a value that arises when an operation is not closed (e.g., $0.0/0.0$ yields `NaN`). (Technically `NaN` has several possible "values," a point ignored here.) Except for negative bases, the floating point numbers with the addition of `Inf` and `NaN` are closed under the operations `+`, `-`, `*`, `/`, and `^`. Here are some computations that produce `NaN`:
|
||||
|
||||
|
||||
```{julia}
|
||||
0/0, Inf/Inf, Inf - Inf, 0 * Inf
|
||||
```
|
||||
|
||||
Whereas, these produce an infinity
|
||||
|
||||
|
||||
```{julia}
|
||||
1/0, Inf + Inf, 1 * Inf
|
||||
```
|
||||
|
||||
Finally, these are mathematically undefined, but still yield a finite value with `Julia`:
|
||||
|
||||
|
||||
```{julia}
|
||||
0^0, Inf^0
|
||||
```
|
||||
|
||||
#### Floating point numbers and real numbers
|
||||
|
||||
|
||||
Floating point numbers are an abstraction for the real numbers. For the most part this abstraction works in the background, though there are cases where one needs to have it in mind. Here are a few:
|
||||
|
||||
|
||||
* For real and rational numbers, between any two numbers $a < b$, there is another real number in between. This is not so for floating point numbers which have a finite precision. (Julia has some functions for working with this distinction.)
|
||||
* Floating point numbers are approximations for most values, even simple rational ones like $1/3$. This leads to oddities such as this value not being $0$:
|
||||
|
||||
|
||||
```{julia}
|
||||
sqrt(2)*sqrt(2) - 2
|
||||
```
|
||||
|
||||
It is no surprise that an irrational number, like $\sqrt{2}$, can't be represented **exactly** within floating point, but it is perhaps surprising that simple numbers can not be, so $1/3$, $1/5$, $\dots$ are approximated. Here is a surprising-at-first consequence:
|
||||
|
||||
|
||||
```{julia}
|
||||
1/10 + 2/10 == 3/10
|
||||
```
|
||||
|
||||
That is adding `1/10` and `2/10` is not exactly `3/10`, as expected mathematically. Such differences are usually very small and are generally attributed to rounding error. The user needs to be mindful when testing for equality, as is done above with the `==` operator.
|
||||
|
||||
|
||||
* Floating point addition is not necessarily associative, that is the property $a + (b+c) = (a+b) + c$ may not hold exactly. For example:
|
||||
|
||||
|
||||
```{julia}
|
||||
1/10 + (2/10 + 3/10) == (1/10 + 2/10) + 3/10
|
||||
```
|
||||
|
||||
* Mathematically, for real numbers, subtraction of similar-sized numbers is not exceptional, for example $1 - \cos(x)$ is positive if $0 < x < \pi/2$, say. This will not be the case for floating point values. If $x$ is close enough to $0$, then $\cos(x)$ and $1$ will be so close, that they will be represented by the same floating point value, `1.0`, so the difference will be zero:
|
||||
|
||||
|
||||
```{julia}
|
||||
1.0 - cos(1e-8)
|
||||
```
|
||||
|
||||
### Rational numbers
|
||||
|
||||
|
||||
Rational numbers can be used when the exactness of the number is more important than the speed or wider range of values offered by floating point numbers. In `Julia` a rational number is comprised of a numerator and a denominator, each an integer of the same type, and reduced to lowest terms. The operations of addition, subtraction, multiplication, and division will keep their answers as rational numbers. As well, raising a rational number to an integer value will produce a rational number.
|
||||
|
||||
|
||||
As mentioned, these are constructed using double slashes:
|
||||
|
||||
|
||||
```{julia}
|
||||
1//2, 2//1, 6//4
|
||||
```
|
||||
|
||||
Rational numbers are exact, so the following are identical to their mathematical counterparts:
|
||||
|
||||
|
||||
```{julia}
|
||||
1//10 + 2//10 == 3//10
|
||||
```
|
||||
|
||||
and associativity:
|
||||
|
||||
|
||||
```{julia}
|
||||
(1//10 + 2//10) + 3//10 == 1//10 + (2//10 + 3//10)
|
||||
```
|
||||
|
||||
Here we see that the type is preserved under the basic operations:
|
||||
|
||||
|
||||
```{julia}
|
||||
(1//2 + 1//3 * 1//4 / 1//5) ^ 6
|
||||
```
|
||||
|
||||
For powers, a non-integer exponent is converted to floating point, so this operation is defined, though will always return a floating point value:
|
||||
|
||||
|
||||
```{julia}
|
||||
(1//2)^(1//2) # the first parentheses are necessary as `^` will be evaluated before `//`.
|
||||
```
|
||||
|
||||
##### Example: different types of real numbers
|
||||
|
||||
|
||||
This table shows what attributes are implemented for the different types.
|
||||
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
using DataFrames
|
||||
attributes = ["construction", "exact", "wide range", "has infinity", "has `-0`", "fast", "closed under"]
|
||||
integer = [q"1", "true", "false", "false", "false", "true", "`+`, `-`, `*`, `^` (non-negative exponent)"]
|
||||
rational = ["`1//1`", "true", "false", "false", "false", "false", "`+`, `-`, `*`, `/` (non zero denominator),`^` (integer power)"]
|
||||
float = [q"1.0", "not usually", "true", "true", "true", "true", "`+`, `-`, `*`, `/` (possibly `NaN`, `Inf`),`^` (non-negative base)"]
|
||||
d = DataFrame(Attributes=attributes, Integer=integer, Rational=rational, FloatingPoint=float)
|
||||
table(d)
|
||||
```
|
||||
|
||||
### Complex numbers
|
||||
|
||||
|
||||
Complex numbers in `Julia` are stored as two numbers, a real and imaginary part, each some type of `Real` number. The special constant `im` is used to represent $i=\sqrt{-1}$. This makes the construction of complex numbers fairly standard:
|
||||
|
||||
|
||||
```{julia}
|
||||
1 + 2im, 3 + 4.0im
|
||||
```
|
||||
|
||||
(These two aren't exactly the same, the `3` is promoted from an integer to a float to match the `4.0`. Each of the components must be of the same type of number.)
|
||||
|
||||
|
||||
Mathematically, complex numbers are needed so that certain equations can be satisfied. For example $x^2 = -2$ has solutions $-\sqrt{2}i$ and $\sqrt{2}i$ over the complex numbers. Finding this in `Julia` requires some attention, as we have both `sqrt(-2)` and `sqrt(-2.0)` throwing a `DomainError`, as the `sqrt` function expects non-negative real arguments. However first creating a complex number does work:
|
||||
|
||||
|
||||
```{julia}
|
||||
sqrt(-2 + 0im)
|
||||
```
|
||||
|
||||
For complex arguments, the `sqrt` function will return complex values (even if the answer is a real number).
|
||||
|
||||
|
||||
This means, if you wanted to perform the quadratic equation for any real inputs, your computations might involve something like the following:
|
||||
|
||||
|
||||
```{julia}
|
||||
a,b,c = 1,2,3 ## x^2 + 2x + 3
|
||||
discr = b^2 - 4a*c
|
||||
(-b + sqrt(discr + 0im))/(2a), (-b - sqrt(discr + 0im))/(2a)
|
||||
```
|
||||
|
||||
When learning calculus, the only common usage of complex numbers arises when solving polynomial equations for roots, or zeros, though they are very important for subsequent work using the concepts of calculus.
|
||||
|
||||
|
||||
:::{.callout-note}
|
||||
## Note
|
||||
Though complex numbers are stored as pairs of numbers, the imaginary unit, `im`, is of type `Complex{Bool}`, a type that can be promoted to more specific types when `im` is used with different number types.
|
||||
|
||||
:::
|
||||
|
||||
### Strings and symbols
|
||||
|
||||
For text, `Julia` has a `String` type. When double quotes are used to specify a string, the parser creates this type:
|
||||
|
||||
```{julia}
|
||||
x = "The quick brown fox jumped over the lazy dog"
|
||||
typeof(x)
|
||||
```
|
||||
|
||||
Values can be inserted into a string through *interpolation* using a dollar sign.
|
||||
|
||||
```{julia}
|
||||
animal = "lion"
|
||||
x = "The quick brown $(animal) jumped over the lazy dog"
|
||||
```
|
||||
|
||||
The use of parentheses allows more complicated expressions; it isn't always necessary.
|
||||
|
||||
Longer strings can be produced using *triple* quotes:
|
||||
|
||||
```{julia}
|
||||
lincoln = """
|
||||
Four score and seven years ago our fathers brought forth, upon this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.
|
||||
"""
|
||||
```
|
||||
|
||||
Strings are comprised of *characters* which can be produced directly using *single* quotes:
|
||||
|
||||
```{julia}
|
||||
'c'
|
||||
```
|
||||
|
||||
We won't use these.
|
||||
|
||||
Finally, `Julia` has *symbols* which are *interned* strings which are used as identifiers. Symbols are used for advanced programming techniques; we will only see them as shortcuts to specify plotting arguments.
|
||||
|
||||
## Type stability
|
||||
|
||||
|
||||
One design priority of `Julia` is that it should be fast. How can `Julia` do this? In a simple model, `Julia` is an interface between the user and the computer's processor(s). Processors consume a set of instructions, the user issues a set of commands. `Julia` is in charge of the translation between the two. Ultimately `Julia` calls a compiler to create the instructions. A basic premise is the shorter the instructions, the faster they are to process. Shorter instructions can come about by being more explicit about what types of values the instructions concern. Explicitness means, there is no need to reason about what a value can be. When `Julia` can reason about the type of value involved without having to reason about the values themselves, it can work with the compiler to produce shorter lists of instructions.
|
||||
|
||||
|
||||
So knowing the type of the output of a function based only on the type of the inputs can be a big advantage. In `Julia` this is known as *type stability*. In the standard `Julia` library, this is a primary design consideration.
|
||||
|
||||
|
||||
##### Example: closure
|
||||
|
||||
|
||||
To motivate this a bit, we discuss how mathematics can be shaped by a desire to stick to simple ideas. A desirable algebraic property of a set of numbers and an operation is *closure*. That is, if one takes an operation like `+` and then uses it to add two numbers in a set, will that result also be in the set? If this is so for any pair of numbers, then the set is closed with respect to the operation addition.
|
||||
|
||||
|
||||
Lets suppose we start with the *natural numbers*: $1,2, \dots$. Natural, in that we can easily represent small values in terms of fingers. This set is closed under addition - as a child learns when counting using their fingers. However, if we started with the odd natural numbers, this set would *not* be closed under addition - $3+3=6$.
|
||||
|
||||
|
||||
The natural numbers are not all the numbers we need, as once a desire for subtraction is included, we find the set isn't closed. There isn't a $0$, needed as $n-n=0$ and there aren't negative numbers. The set of integers are needed for closure under addition and subtraction.
|
||||
|
||||
|
||||
The integers are also closed under multiplication, which for integer values can be seen as just regrouping into longer additions.
|
||||
|
||||
|
||||
However, the integers are not closed under division - even if you put aside the pesky issue of dividing by $0$. For that, the rational numbers must be introduced. So aside from division by $0$, the rationals are closed under addition, subtraction, multiplication, and division. There is one more fundamental operation though, powers.
|
||||
|
||||
|
||||
Powers are defined for positive integers in a simple enough manner
|
||||
|
||||
|
||||
$$
|
||||
a^n=a \cdot a \cdot a \cdots a \text{ (n times); } a, n \text{ are integers } n \text{ is positive}.
|
||||
$$
|
||||
|
||||
We can define $a^0$ to be $1$, except for the special case of $0^0$, which is left undefined mathematically (though it is also defined as `1` within `Julia`). We can extend the above to include negative values of $a$, but what about negative values of $n$? We can't say the integers are closed under powers, as the definition consistent with the rules that $a^{(-n)} = 1/a^n$ requires rational numbers to be defined.
|
||||
|
||||
|
||||
Well, in the above `a` could be a rational number, is `a^n` closed for rational numbers? No again. Though it is fine for $n$ as an integer (save the odd case of $0$, simple definitions like $2^{1/2}$ are not answered within the rationals. For this, we need to introduce the *real* numbers. It is mentioned that [Aristotle](http://tinyurl.com/bpqbkap) hinted at the irrationality of the square root of $2$. To define terms like $a^{1/n}$ for integer values $a,n > 0$ a reference to a solution to an equation $x^n-a$ is used. Such solutions require the irrational numbers to have solutions in general. Hence the need for the real numbers (well, algebraic numbers at least, though once the exponent is no longer a rational number, the full set of real numbers are needed.)
|
||||
|
||||
|
||||
So, save the pesky cases, the real numbers will be closed under addition, subtraction, multiplication, division, and powers - provided the base is non-negative.
|
||||
|
||||
|
||||
Finally for that last case, the complex numbers are introduced to give an answer to $\sqrt{-1}$.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
How does this apply with `Julia`?
|
||||
|
||||
|
||||
The point is, if we restrict our set of inputs, we can get more precise values for the output of basic operations, but to get more general inputs we need to have bigger output sets.
|
||||
|
||||
|
||||
A similar thing happens in `Julia`. For addition say, the addition of two integers of the same type will be an integer of that type. This speed consideration is not solely for type stability, but also to avoid checking for overflow.
|
||||
|
||||
|
||||
Another example, the division of two integers will always be a number of the same type - floating point, as that is the only type that ensures the answer will always fit within. (The explicit use of rationals notwithstanding.) So even if two integers are the input and their answer *could* be an integer, in `Julia` it will be a floating point number, (cf. `2/1`).
|
||||
|
||||
|
||||
Hopefully this helps explain the subtle issues around powers: in `Julia` an integer raised to an integer should be an integer, for speed, though certain cases are special cased, like `2^(-1)`. However since a real number raised to a real number makes sense always when the base is non-negative, as long as real numbers are used as outputs, the expressions `2.0^(-1)` and `2^(-1.0)` are computed and real numbers (floating points) are returned. For type stability, even though $2.0^1$ could be an integer, a floating point answer is returned.
|
||||
|
||||
|
||||
As for negative bases, `Julia` could always return complex numbers, but in addition to this being slower, it would be irksome to users. So user's must opt in. Hence `sqrt(-1.0)` will be an error, but the more explicit - but mathematically equivalent - `sqrt(-1.0 + 0im)` will not be a domain error, but rather a complex value will be returned.
|
||||
|
||||
|
||||
## Questions
|
||||
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["Integer", "Rational", "Floating point", "Complex", "None, an error occurs"]
|
||||
nothing
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `pi/2` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `2/2` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `2//2` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 2
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `1 + 1//2 + 1/3` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `2^3` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 1
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `sqrt(im)` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 4
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `2^(-1)` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The "number" created by `1/0` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Is `(2 + 6) + 7` equal to `2 + (6 + 7)`?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
yesnoq(true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Is `(2/10 + 6/10) + 7/10` equal to `2/10 + (6/10 + 7/10)`?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
yesnoq(false)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The following *should* compute `2^(-1)`, which if entered directly will return `0.5`. Does it?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| eval: false
|
||||
a, b = 2, -1
|
||||
a^b
|
||||
```
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
yesnoq(false)
|
||||
```
|
||||
|
||||
(This shows the special casing that is done when powers use literal numbers.)
|
||||
|
||||
|
||||
###### Question
|
||||
|
||||
In [NewScientist](https://www.newscientist.com/article/2112537-smallest-sliver-of-time-yet-measured-sees-electrons-fleeing-atom/) we learn "For the first time, physicists have measured changes in an atom to the level of zeptoseconds, or trillionths of a billionth of a second – the smallest division of time yet observed."
|
||||
|
||||
That is
|
||||
|
||||
```{julia}
|
||||
1e-9 / 1e12
|
||||
```
|
||||
|
||||
Finding the value through division introduces a floating point deviation. Which of the following values will directly represent a zeptosecond?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
as = ["1/10^21", "1e-21"]
|
||||
explanation = "The scientific notation is correct. Due to integer overflow `10^21` is not the same number as `10.0^21`."
|
||||
buttonq(as, 2; explanation)
|
||||
```
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,675 @@
|
||||
# Number systems
|
||||
|
||||
|
||||
{{< include ../_common_code.qmd >}}
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
#| results: "hidden"
|
||||
using CalculusWithJulia
|
||||
|
||||
nothing
|
||||
```
|
||||
|
||||
In mathematics there are many different number systems in common use. For example by the end of pre-calculus, all of the following have been introduced:
|
||||
|
||||
|
||||
* The integers, $\{\dots, -3, -2, -1, 0, 1, 2, 3, \dots\}$;
|
||||
|
||||
* The rational numbers, $\{p/q: p, q \text{ are integers}, q \neq 0\}$;
|
||||
|
||||
* The real numbers, $\{x: -\infty < x < \infty\}$;
|
||||
|
||||
* The complex numbers, $\{a + bi: a,b \text{ are real numbers and } i^2=-1\}$.
|
||||
|
||||
|
||||
On top of these, we have special subsets, such as the natural numbers $\{1, 2, \dots\}$ (sometimes including $0$), the even numbers, the odd numbers, the positive numbers, the non-negative numbers, etc.
|
||||
|
||||
|
||||
Mathematically, these number systems are naturally nested within each other as integers are rational numbers which are real numbers, which can be viewed as part of the complex numbers.
|
||||
|
||||
|
||||
Calculators typically have just one type of number---floating point values. These model the real numbers.
|
||||
|
||||
`Julia`, on the other hand, has a rich type system, and within that has several different number types. There are types that model each of the four main systems above, and within each type, specializations for how these values are stored.
|
||||
|
||||
|
||||
For now, let's consider the number $1$. It can be viewed as either an integer, rational, real, or complex number. To construct "$1$" in each type within `Julia` we have these different styles:
|
||||
|
||||
|
||||
```{julia}
|
||||
1, 1.0, 1//1, 1 + 0im
|
||||
```
|
||||
|
||||
The basic number types in `Julia` are `Int`, `Float64`, `Rational` and `Complex`, though in fact there are many more, and the last two aren't even *concrete* types. This distinction is important, as the type of number dictates how it will be displayed, how it will be stored, and how precisely the stored value can be expected to be to the mathematical value it models.
|
||||
|
||||
|
||||
Though there are explicit constructors for these types, these notes avoid them unless necessary, as `Julia`'s parser can distinguish these types through an easy to understand syntax:
|
||||
|
||||
|
||||
* integers have no decimal point;
|
||||
|
||||
* floating point numbers have a decimal point (or are written with scientific notation);
|
||||
|
||||
* rationals are constructed from integers using the double division operator, `//`; and
|
||||
|
||||
* complex numbers are formed by including a term with the imaginary unit, `im`.
|
||||
|
||||
|
||||
:::{.callout-note}
|
||||
## Warning
|
||||
Heads up, the difference between `1` and `1.0` is subtle. Even more so, as `1.` will parse as `1.0`. This means some expressions, such as `2.*3`, are ambiguous, as the `.` might be part of the `2` (as in `2. * 3`) or the operation `*` (as in `2 .* 3`).
|
||||
|
||||
:::
|
||||
|
||||
|
||||
The key distinction is between integers and floating points. While floating point values include integers, and so can be used exclusively on the calculator, the difference is that an integer is guaranteed to be an exact value, whereas a floating point value, while often an exact representation of a number is also often just an *approximate* value. This can be an advantage---floating point values can model a much wider range of numbers.
|
||||
|
||||
In nearly all cases the differences are not noticeable. To see why take, for instance, this simple calculation involving mixed types.
|
||||
|
||||
|
||||
```{julia}
|
||||
1 + 1.25 + 3//2
|
||||
```
|
||||
|
||||
The sum of an integer, a floating point number and rational number returns a floating point number without a complaint.
|
||||
|
||||
### Promotion
|
||||
|
||||
This is because behind the scenes, `Julia` will often "promote" the two numbers to a common type. In particular, before adding mixed-type numbers, the two are promoted to a common type by `promote`. In the example, first when computing `1 + 1.25` the integer `1` will be promoted to a floating point value, `1.0`, and then the two values are added. Similarly, with `2.25 + 3//2`, where the fraction is promoted to the floating point value `1.5` and afterwards addition is carried out.
|
||||
|
||||
We can see the promotion here:
|
||||
|
||||
```{julia}
|
||||
promote(1, 1.25)
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```{julia}
|
||||
promote(2.25, 3//2)
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Integers
|
||||
|
||||
Integers are often used casually, as they come about from parsing. As with a calculator, floating point numbers *could* be used for integers, but in `Julia`---and other languages---it proves useful to have numbers known to have *exact* values. Integers are needed for indexing and counting.
|
||||
|
||||
Except on older machines, the default integer is stored with 64 bits, though there are many available types for integers. With $64$ bits, the range of integers that can be represented is $-9223372036854775808=-(2^{63})$ to $9223372036854775807 = 2^{63}-1$.
|
||||
|
||||
## Floating point numbers
|
||||
|
||||
Floating point numbers are a model for the real numbers. With the same size storage, the integers provide exact numbers evenly spaced between the smallest and largest integer. Floating point values are exact for some values but as there are infinitely many real numbers are only approximations except in special cases. This leads to some differences between math done by hand and math done on the computer.
|
||||
|
||||
### Float64
|
||||
|
||||
`Float64` is the most common type of floating point number, as it is the most supported by the underlying hardware. `Julia` has other floating point types, notably `Float32` and `BigFloat` for certain uses, but our focus here is on 64-bit floating point numbers.
|
||||
|
||||
The double-precision model for floating point numbers has three parts: a sign, an exponent (for a base of $2$), and a significand (in base $2$) representing numbers as $\pm a \cdot 2^n$. The 64 bits are apportioned as follows: $1$ is for the sign, $11$ for the exponent, $52$ for the significand.
|
||||
|
||||
|
||||
The $52$ bits of the significand are used to represent $1.a_1a_2a_3\cdots a_{52}$ in base $2$ or $1 + a_12^{-1} + a_22^{-2} + a_32^{-3} + \cdots a_{52}2^{-52} = b/2^{52}$ for some integer $b$. This means the significand represents a rational number.
|
||||
|
||||
The following shows the bits in the significand for a given number written in the form above:^[The output of `bitstring` is 64 characters. The first is the sign bit, the second through twelfth the exponent, the rest the significand. The notation `[13:end]` is used to return just those for the significand. A value of `[2:12]` would return the bits for the exponent.]
|
||||
|
||||
```{julia}
|
||||
bitstring(1 + 1/2 + 1/4 + 0/8 + 1/16 + 1/32 + 0/64)[13:end] # 1101100…
|
||||
```
|
||||
|
||||
|
||||
The 11 bits for the exponent covers a range from $-1023$ to $1024$ which in base $10$ is around $10^{-308}$ to $10^{308}$.
|
||||
|
||||
Together these can represent exactly any *rational* number of the form $\pm a \cdot 2^b$ where $a$ is a sum of powers of $1/2$ and $b$ is an integer with $1.0 \leq a \leq 1 + (1/2^1) + (1/2^2) + \cdots + (1/2^{52})$ and $-1023 \leq b \leq 1024$.
|
||||
|
||||
@fig-floating-point-concentration shows the possible positive values *were* there only $2$ bits for the exponent (for $-1, 0, 1, 2$) and $2$ bits for the significand ($1 + 0/4 + 0/2$, $1 + 1/4 + 0/2$, $1 + 0/4 + 1/2$, $1 + 1/4 + 1/2$). The main takeaway is that numbers get less concentrated the farther they get from $0$.
|
||||
|
||||
::: {#fig-floating-point-concentration layout-ncol=1}
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
#| fig: floating_point_concentration
|
||||
#| fig-label: floating point concentration
|
||||
using Plots
|
||||
gr()
|
||||
using LaTeXStrings
|
||||
xvals = [0, 1/2, 1, 2, 4, 8]
|
||||
xlabels = [L"0", L"\frac{1}{2}", L"1", L"2", L"4", L"8"]
|
||||
p = plot(; size=(600, 200), xlim = (0, 8.5), ylim=(-1, 1),
|
||||
xaxis=([], false),
|
||||
yaxis=([], false),
|
||||
framestyle=:origin,
|
||||
legend=false,
|
||||
)
|
||||
|
||||
plot!(p, [(0,0), (8.5,0)]; arrow=true, line=(2, :black))
|
||||
xs = range(0, 1 - 1/2, 5)[1:end-1]
|
||||
for (i,xi) in enumerate(xvals[2:end-1])
|
||||
for x in xs
|
||||
x₀ = xi + 2^(i-0)*x/2
|
||||
Δ = 1/20
|
||||
plot!(p, [(x₀, -Δ), (x₀, 2Δ)]; linecolor=:black)
|
||||
end
|
||||
end
|
||||
|
||||
plot!(p; annotations=tuple.(xvals, zero.(xvals), text.(xlabels, :top)))
|
||||
p
|
||||
```
|
||||
|
||||
Figure showing concentration of floating point values. The vertical ticks represent representable floating point values (were there only 2 bits (not 52) for the mantissa) and 2 bits (not 11) for the exponent. This leaves a range from a range of $-1/2$ to not quite $8$ being representable *without* using subnormal numbers. When the intervals double in length (from $[2^{i},2^{i+1}]$ to $[2^{i+1}, 2^{i+2}]$) there are the same number of representable floating point values, so the concentration of representable values halves. With more bits there is a higher concentration, but the discrete nature is always present and leads to necessary approximations for modeling most all real numbers.
|
||||
:::
|
||||
|
||||
In addition, there are special bit patterns recognized as `0.0` and even `-0.0`, which is a distinct number. There are also patterns for $+\infty$ (`Inf`) and $-\infty$ (`-Inf`). There are also patterns for `NaN`, or "not a number", a value that is the result of some mathematical operations, such as `0.0 / 0.0`. Finally, there are *subnormal* numbers representing even smaller numbers near $0$ than described above, which are as small as $2^{-1023} \approx 1.11 \cdot 10^{-308}$.
|
||||
|
||||
|
||||
### Scientific notation
|
||||
|
||||
Floating point numbers smaller than $10^{-4}$ or bigger or equal to $10^6$ (in absolute value) are displayed in scientific notation. Internally, most floating point numbers are stored in base $2$ scientific notation as $a \cdot 2^b$ with $a=1.xxx\dots$. But when displayed, numbers are represented in base $10$ and when scientific notation is used the numbers are normalized in the from $a \cdot 10^b$ where $1.0 \leq a < 10$.
|
||||
|
||||
The significand and exponent are separated by the character `e`---which is not the same as the constant $e$---rather denotes a 64-bit number separated into a significand and an exponent by a formatting character. (`Float32` uses an `f` as a separator.)
|
||||
|
||||
Consider these two numbers one close to $0$ one far from $0$:
|
||||
|
||||
```{julia}
|
||||
0.0000000123456789, 123456789.0
|
||||
```
|
||||
|
||||
Their display is subtly different, as only a minus sign after `e` distinguishes them.
|
||||
|
||||
The parser will read in numbers with an `e` in the proper format as though they are scientific notation:
|
||||
|
||||
```{julia}
|
||||
1e8
|
||||
```
|
||||
|
||||
The above creates the same value as `10.0^8`, but not `1e^8` which will error unless a value for `e` has been assigned.
|
||||
|
||||
### Inexactness and consequences
|
||||
|
||||
For numbers not representable in floating point, some rounding must go on to fit the number into a representable floating point value. As such, some computed values are not quite what they would be mathematically:
|
||||
|
||||
```{julia}
|
||||
sqrt(2) * sqrt(2) - 2, sin(1pi)
|
||||
```
|
||||
|
||||
These values are *very* small numbers, but not exactly $0$, as they are mathematically.
|
||||
|
||||
More surprisingly, simple fractions may also lead to mathematically different results:
|
||||
|
||||
```{julia}
|
||||
1/10 + 2/10 - 3/10
|
||||
```
|
||||
|
||||
This, of course, is due to none of these fractions being of the form $a\cdot 2^b$ for integers $a, b$.
|
||||
|
||||
Another surprise: floating point addition is not necessarily associative. That is the property $a + (b+c) = (a+b) + c$ may not hold exactly. For example:
|
||||
|
||||
|
||||
```{julia}
|
||||
l2r = (1/10 + 2/10) + 3/10
|
||||
r2l = 1/10 + (2/10 + 3/10)
|
||||
l2r - r2l
|
||||
```
|
||||
|
||||
One other surprise. Mathematically, for real numbers, subtraction of similar-sized numbers is not exceptional, for example $1 - \cos(x)$ is positive if $0 < x < \pi/2$, say. This will not be the case for floating point values. If $x$ is close enough to $0$, then $\cos(x)$ and $1$ will be so close, that they will be represented by the same floating point value, `1.0`, so the difference will be zero:
|
||||
|
||||
|
||||
```{julia}
|
||||
1.0 - cos(1e-8)
|
||||
```
|
||||
|
||||
## Rational numbers
|
||||
|
||||
|
||||
Rational numbers can be used when the exactness of the number is more important than the speed or wider range of values offered by floating point numbers. In `Julia` a rational number is comprised of a numerator and a denominator, each an integer of the same type, and reduced to lowest terms. The operations of addition, subtraction, multiplication, and division will keep their answers as rational numbers. As well, raising a rational number to an integer value will produce a rational number.
|
||||
|
||||
|
||||
As mentioned, these are constructed using double slashes:
|
||||
|
||||
|
||||
```{julia}
|
||||
1//2, 2//1, 6//4
|
||||
```
|
||||
|
||||
Rational numbers are exact, so the following are identical to their mathematical counterparts:
|
||||
|
||||
|
||||
```{julia}
|
||||
1//10 + 2//10 == 3//10
|
||||
```
|
||||
|
||||
and associativity:
|
||||
|
||||
|
||||
```{julia}
|
||||
(1//10 + 2//10) + 3//10 == 1//10 + (2//10 + 3//10)
|
||||
```
|
||||
|
||||
Here we see that the type is preserved under the basic operations:
|
||||
|
||||
|
||||
```{julia}
|
||||
(1//2 + 1//3 * 1//4 / 1//5) ^ 6
|
||||
```
|
||||
|
||||
For powers, a non-integer exponent is converted to floating point, so this operation is defined, though will always return a floating point value:
|
||||
|
||||
|
||||
```{julia}
|
||||
(1//2)^(1//2) # the first parentheses are necessary as `^` will be evaluated before `//`.
|
||||
```
|
||||
|
||||
## Complex numbers
|
||||
|
||||
Complex numbers in `Julia` are stored as two numbers, a real and imaginary part, each some type of `Real` number. The special constant `im` is used to represent $i=\sqrt{-1}$. This makes the construction of complex numbers fairly standard:
|
||||
|
||||
|
||||
```{julia}
|
||||
1 + 2im, 3 + 4.0im
|
||||
```
|
||||
|
||||
(These two aren't exactly the same, the `3` is promoted from an integer to a float to match the `4.0`. Each of the components must be of the same type of number.)
|
||||
|
||||
|
||||
Mathematically, complex numbers are needed so that certain equations can be satisfied. For example $x^2 = -2$ has solutions $-\sqrt{2}i$ and $\sqrt{2}i$ over the complex numbers. Finding this in `Julia` requires some attention, as we have both `sqrt(-2)` and `sqrt(-2.0)` throwing a `DomainError`, as the `sqrt` function expects non-negative real arguments. However first creating a complex number and then taking a square root does work:
|
||||
|
||||
|
||||
```{julia}
|
||||
sqrt(-2 + 0im)
|
||||
```
|
||||
|
||||
For complex arguments, the `sqrt` function will return complex values (even if the answer is a real number).
|
||||
|
||||
|
||||
This means, if you wanted to perform the quadratic equation for any real inputs, your computations might involve something like the following:
|
||||
|
||||
|
||||
```{julia}
|
||||
a,b,c = 1,2,3 ## x^2 + 2x + 3
|
||||
discr = b^2 - 4a*c
|
||||
(-b + sqrt(discr + 0im))/(2a), (-b - sqrt(discr + 0im))/(2a)
|
||||
```
|
||||
|
||||
When learning calculus, the only common usage of complex numbers arises when solving polynomial equations for roots, or zeros, though they are very important for subsequent work using the concepts of calculus.
|
||||
|
||||
## Irrational numbers
|
||||
|
||||
`Julia` has a a few mathematical constants that are stored with a special type `Irrational`. One such value is `pi`. There are others in the `Base.MathConstants` module, and an external package `IrrationalConstants.jl`.
|
||||
|
||||
`Irrational` values may have special methods defined for them which can lead to subtle differences, such as:
|
||||
|
||||
```{julia}
|
||||
sin(pi), sin(2pi)
|
||||
```
|
||||
|
||||
In computing the product `2pi` first the two values are promoted to `Float64` and then multiplied, leaving a floating-point approximation of $2\pi$ for `sin` to evaluate.
|
||||
|
||||
## Other types of data: strings and symbols
|
||||
|
||||
|
||||
For text, `Julia` has a `String` type. When double quotes are used to specify a string, the parser creates this type:
|
||||
|
||||
```{julia}
|
||||
x = "The quick brown fox jumped over the lazy dog"
|
||||
typeof(x)
|
||||
```
|
||||
|
||||
Values can be inserted into a string through *interpolation* using a dollar sign.
|
||||
|
||||
```{julia}
|
||||
animal = "lion"
|
||||
x = "The quick brown $(animal) jumped over the lazy dog"
|
||||
```
|
||||
|
||||
The use of parentheses allows more complicated expressions; it isn't always necessary.
|
||||
|
||||
Longer strings can be produced using *triple* quotes:
|
||||
|
||||
```{julia}
|
||||
lincoln = """
|
||||
Four score and seven years ago our fathers brought forth, upon this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.
|
||||
"""
|
||||
```
|
||||
|
||||
Strings are comprised of *characters* which can be produced directly using *single* quotes:
|
||||
|
||||
```{julia}
|
||||
'c'
|
||||
```
|
||||
|
||||
We won't use characters in these notes.
|
||||
|
||||
Finally, `Julia` has *symbols* which are *interned* strings which are used as identifiers. Symbols are used for advanced programming techniques; we will only see them as shortcuts to specify plotting arguments.
|
||||
|
||||
|
||||
|
||||
|
||||
## Questions
|
||||
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["Integer", "Rational", "Floating point", "Complex", "None, an error occurs"]
|
||||
nothing
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `pi/2` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `2/2` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `2//2` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 2
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `1 + 1//2 + 1/3` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `2^3` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 1
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `sqrt(im)` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 4
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `2^(-1)` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The "number" created by `1/0` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Is `(2 + 6) + 7` equal to `2 + (6 + 7)`?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
yesnoq(true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Is `(2/10 + 6/10) + 7/10` equal to `2/10 + (6/10 + 7/10)`?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
yesnoq(false)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The following *should* compute `2^(-1)`, which if entered directly will return `0.5`. Does it?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| eval: false
|
||||
a, b = 2, -1
|
||||
a^b
|
||||
```
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
yesnoq(false)
|
||||
```
|
||||
|
||||
(This shows the special casing that is done when powers use literal numbers.)
|
||||
|
||||
|
||||
###### Question
|
||||
|
||||
In [NewScientist](https://www.newscientist.com/article/2112537-smallest-sliver-of-time-yet-measured-sees-electrons-fleeing-atom/) we learn "For the first time, physicists have measured changes in an atom to the level of zeptoseconds, or trillionths of a billionth of a second---the smallest division of time yet observed."
|
||||
|
||||
That is
|
||||
|
||||
```{julia}
|
||||
1e-9 / 1e12
|
||||
```
|
||||
|
||||
Finding the value through division introduces a floating point deviation. Which of the following values will directly represent a zeptosecond?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
as = ["1/10^21", "1e-21"]
|
||||
explanation = "The scientific notation is correct. Due to integer overflow `10^21` is not the same number as `10.0^21`."
|
||||
buttonq(as, 2; explanation)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
Signed integers are stored on a computer in a special manner. We will see with 8 bit integers, formed by `Int8`. Eight bit means only 8 0's or 1's are used to store a given number. This is a useful format for storing many small integers but for this example, useful as we can more easily track the values.
|
||||
|
||||
The first bit is a sign bit. Based on these two outputs, can you guess how that works:
|
||||
|
||||
```{julia}
|
||||
bitstring(Int8(-1)), bitstring(Int8(1))
|
||||
```
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["The first digit is `1` for positive numbers, `0` for negative numbers",
|
||||
"The first digit is `0` for positive numbers, `1` for negative numbers"]
|
||||
answer = 2
|
||||
buttonq(choices, answer)
|
||||
```
|
||||
|
||||
Positive numbers and negative numbers are stored a bit differently. Positive numbers just use binary: $a_0 \cdot 2^0 + a_1 \cdot 2^1 + a_2 \cdot 2^2 + \cdots a_7 \cdot 2^7$. The number $27$ is $1 + 2 + 8 + 16$. so have $a_0 = a_1 = a_3 = a_4 = 1$, the others are $0$. The bitstring shows:
|
||||
|
||||
```{julia}
|
||||
bitstring(Int8(27))
|
||||
```
|
||||
|
||||
Which bit pattern is used?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["`sign bit | a₀ | a₁ | a₂ | a₃ | a₄ | a₅ | a₆ | a₇`",
|
||||
"`sign bit | a₇ | a₆ | a₅ | a₄ | a₃ | a₂ | a₁ | a₀`"]
|
||||
answer = 2
|
||||
buttonq(choices, answer)
|
||||
```
|
||||
|
||||
Negative numbers are stored using *two's complement* format:
|
||||
|
||||
* represent the positive number
|
||||
* flip `0` to `1`; `1` to `0`
|
||||
* add `1` to the value (long addition with carrying)
|
||||
|
||||
For $-27$ we have
|
||||
|
||||
* first $27$: `00011011`
|
||||
* then flip each: `11100100`
|
||||
* finally add $1$: `11100101`
|
||||
|
||||
```{julia}
|
||||
bitstring(Int8(-27))
|
||||
```
|
||||
|
||||
The largest positive number is $127$ for 8-bits and is represented by `01111111`. What is the bit pattern of $-127$?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["`01111111 => 10000000 => 10000001`",
|
||||
"`01111111 => 10000000 => 100000000`"]
|
||||
answer = 1
|
||||
explanation = "Add the `1` on the right side, not the left side"
|
||||
buttonq(choices, answer; explanation)
|
||||
```
|
||||
|
||||
The smallest negative number is $-128$. Why?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["Subtracting 1 from `10000001` leaves `10000000` which is the smallest negative number",
|
||||
"Tricky, the smallest negative number is ``-127``"
|
||||
]
|
||||
answer = 1
|
||||
buttonq(choices, answer)
|
||||
```
|
||||
|
||||
Why all this fuss? Couldn't there be an easier way?
|
||||
|
||||
This storage has a big advantage when adding numbers. Let's look at adding $-5$ to $6$. we have:
|
||||
|
||||
```
|
||||
-5 => 11111011
|
||||
6 => 00000110
|
||||
--------
|
||||
100000001 => 00000001
|
||||
```
|
||||
|
||||
The addition is done by carrying a `1` across and then *dropping* the 9th number when there is such a carry. This leaves the representation for what number?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["`1`", "`-1`", "`0`"]
|
||||
answer = 1
|
||||
buttonq(choices, answer)
|
||||
```
|
||||
|
||||
|
||||
The largest *positive* number that can be represented is $2^0 + 2^1 + 2^2 + \cdots + 2^7$, where $7$ is the number of bits minus $1$. The representation is `01111111`. What happens if we add `1` to this number?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["The value after carrying is `100000000`. After dropping the 9th number, we get `00000000` or zero",
|
||||
"The value after carrying is `10000000` or ``-128``"]
|
||||
answer = 2
|
||||
explanation = "Addition wraps around from the largest to the smallest and goes from there."
|
||||
buttonq(choices, answer)
|
||||
```
|
||||
|
||||
The largest possible number for a type is returned by `typemax`. For `Int64` (just `Int` on most systems) what is the largest number?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
numericq(typemax(Int64))
|
||||
```
|
||||
|
||||
|
||||
###### Question
|
||||
|
||||
The `Float64` type uses $11$ bits for an exponent (base $2$) between $-1023$ and $1024$. We can see how these are stored as follows:
|
||||
|
||||
```{julia}
|
||||
bitstring(2.0^(-1023))[2:12], bitstring(2.0^(1024))[2:12]
|
||||
```
|
||||
|
||||
This is the full range of values. However the values are *shifted* with $0$ representing $-1023$ and $x$ representing $1024$. The value $x$ is can be found from:
|
||||
|
||||
```{julia}
|
||||
#| eval: false
|
||||
2^0 + 2^1 + 2^2 + 2^3 + 2^4 + 2^5 + 2^6 + 2^7 + 2^8 + 2^9 + 2^10
|
||||
```
|
||||
|
||||
What is the value of $x$?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
val = sum(2^i for i in 0:10)
|
||||
numericq(val)
|
||||
```
|
||||
|
||||
The value `1023` is called a bias. The exponent is coded as the binary value as a positve integer minus $1023$. A bias is used, and not the two's complement format, as storage with a bias makes multiplying by powers of $2$ as easy as shifting the bits.
|
||||
|
||||
To find the storage for, say, $2^4 + 2^2 + 2^0$ or `00000010101` we would add `1023` or `01111111111` and see:
|
||||
|
||||
```
|
||||
00000010101
|
||||
+ 01111111111
|
||||
-----------
|
||||
10000010100
|
||||
```
|
||||
|
||||
Which we can see:
|
||||
|
||||
```{julia}
|
||||
bitstring(2.0^(2^4 + 2^2 + 2^0))[2:12]
|
||||
```
|
||||
+327
-317
@@ -11,13 +11,16 @@ using CalculusWithJulia
|
||||
nothing
|
||||
```
|
||||
|
||||
In mathematics, there are many different number systems in common use. For example by the end of pre-calculus, all of the following have been introduced:
|
||||
In mathematics there are many different number systems in common use. For example by the end of pre-calculus, all of the following have been introduced:
|
||||
|
||||
|
||||
* The integers, $\{\dots, -3, -2, -1, 0, 1, 2, 3, \dots\}$;
|
||||
* The rational numbers, $\{p/q: p, q \text{ are integers}, q \neq 0\}$;
|
||||
* The real numbers, $\{x: -\infty < x < \infty\}$;
|
||||
* The complex numbers, $\{a + bi: a,b \text{ are real numbers and } i^2=-1\}$.
|
||||
* The integers, $\{\dots, -3, -2, -1, 0, 1, 2, 3, \dots\}$;
|
||||
|
||||
* The rational numbers, $\{p/q: p, q \text{ are integers}, q \neq 0\}$;
|
||||
|
||||
* The real numbers, $\{x: -\infty < x < \infty\}$;
|
||||
|
||||
* The complex numbers, $\{a + bi: a,b \text{ are real numbers and } i^2=-1\}$.
|
||||
|
||||
|
||||
On top of these, we have special subsets, such as the natural numbers $\{1, 2, \dots\}$ (sometimes including $0$), the even numbers, the odd numbers, the positive numbers, the non-negative numbers, etc.
|
||||
@@ -26,10 +29,9 @@ On top of these, we have special subsets, such as the natural numbers $\{1, 2, \
|
||||
Mathematically, these number systems are naturally nested within each other as integers are rational numbers which are real numbers, which can be viewed as part of the complex numbers.
|
||||
|
||||
|
||||
Calculators typically have just one type of number - floating point values. These model the real numbers. `Julia`, on the other hand, has a rich type system, and within that has several different number types. There are types that model each of the four main systems above, and within each type, specializations for how these values are stored.
|
||||
Calculators typically have just one type of number---floating point values. These model the real numbers.
|
||||
|
||||
|
||||
Most of the details will not be of interest to all, and will be described later.
|
||||
`Julia`, on the other hand, has a rich type system, and within that has several different number types. There are types that model each of the four main systems above, and within each type, specializations for how these values are stored.
|
||||
|
||||
|
||||
For now, let's consider the number $1$. It can be viewed as either an integer, rational, real, or complex number. To construct "$1$" in each type within `Julia` we have these different styles:
|
||||
@@ -39,16 +41,19 @@ For now, let's consider the number $1$. It can be viewed as either an integer, r
|
||||
1, 1.0, 1//1, 1 + 0im
|
||||
```
|
||||
|
||||
The basic number types in `Julia` are `Int`, `Float64`, `Rational` and `Complex`, though in fact there are many more, and the last two aren't even *concrete* types. This distinction is important, as the type of number dictates how it will be stored and how precisely the stored value can be expected to be to the mathematical value it models.
|
||||
The basic number types in `Julia` are `Int`, `Float64`, `Rational` and `Complex`, though in fact there are many more, and the last two aren't even *concrete* types. This distinction is important, as the type of number dictates how it will be displayed, how it will be stored, and how precisely the stored value can be expected to be to the mathematical value it models.
|
||||
|
||||
|
||||
Though there are explicit constructors for these types, these notes avoid them unless necessary, as `Julia`'s parser can distinguish these types through an easy to understand syntax:
|
||||
|
||||
|
||||
* integers have no decimal point;
|
||||
* floating point numbers have a decimal point (or are in scientific notation);
|
||||
* rationals are constructed from integers using the double division operator, `//`; and
|
||||
* complex numbers are formed by including a term with the imaginary unit, `im`.
|
||||
* integers have no decimal point;
|
||||
|
||||
* floating point numbers have a decimal point (or are written with scientific notation);
|
||||
|
||||
* rationals are constructed from integers using the double division operator, `//`; and
|
||||
|
||||
* complex numbers are formed by including a term with the imaginary unit, `im`.
|
||||
|
||||
|
||||
:::{.callout-note}
|
||||
@@ -57,12 +62,10 @@ Heads up, the difference between `1` and `1.0` is subtle. Even more so, as `1.`
|
||||
|
||||
:::
|
||||
|
||||
Similarly, each type is printed slightly differently.
|
||||
|
||||
The key distinction is between integers and floating points. While floating point values include integers, and so can be used exclusively on the calculator, the difference is that an integer is guaranteed to be an exact value, whereas a floating point value, while often an exact representation of a number is also often just an *approximate* value. This can be an advantage---floating point values can model a much wider range of numbers.
|
||||
|
||||
The key distinction is between integers and floating points. While floating point values include integers, and so can be used exclusively on the calculator, the difference is that an integer is guaranteed to be an exact value, whereas a floating point value, while often an exact representation of a number is also often just an *approximate* value. This can be an advantage – floating point values can model a much wider range of numbers.
|
||||
|
||||
In nearly all cases the differences are not noticeable. Take for instance this simple calculation involving mixed types.
|
||||
In nearly all cases the differences are not noticeable. To see why take, for instance, this simple calculation involving mixed types.
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -71,241 +74,151 @@ In nearly all cases the differences are not noticeable. Take for instance this s
|
||||
|
||||
The sum of an integer, a floating point number and rational number returns a floating point number without a complaint.
|
||||
|
||||
### Promotion
|
||||
|
||||
This is because behind the scenes, `Julia` will often "promote" a type to match, so for example to compute `1 + 1.25` the integer `1` will be promoted to a floating point value and the two values are then added. Similarly, with `2.25 + 3//2`, where the fraction is promoted to the floating point value `1.5` and addition is carried out.
|
||||
|
||||
|
||||
As floating point numbers may be approximations, some values are not quite what they would be mathematically:
|
||||
This is because behind the scenes, `Julia` will often "promote" the two numbers to a common type. In particular, before adding mixed-type numbers, the two are promoted to a common type by `promote`. In the example, first when computing `1 + 1.25` the integer `1` will be promoted to a floating point value, `1.0`, and then the two values are added. Similarly, with `2.25 + 3//2`, where the fraction is promoted to the floating point value `1.5` and afterwards addition is carried out.
|
||||
|
||||
We can see the promotion here:
|
||||
|
||||
```{julia}
|
||||
sqrt(2) * sqrt(2) - 2, sin(1pi), 1/10 + 1/5 - 3/10
|
||||
promote(1, 1.25)
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```{julia}
|
||||
promote(2.25, 3//2)
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Integers
|
||||
|
||||
Integers are often used casually, as they come about from parsing. As with a calculator, floating point numbers *could* be used for integers, but in `Julia`---and other languages---it proves useful to have numbers known to have *exact* values. Integers are needed for indexing and counting.
|
||||
|
||||
Except on older machines, the default integer is stored with 64 bits, though there are many available types for integers. With $64$ bits, the range of integers that can be represented is $-9223372036854775808=-(2^{63})$ to $9223372036854775807 = 2^{63}-1$.
|
||||
|
||||
## Floating point numbers
|
||||
|
||||
Floating point numbers are a model for the real numbers. With the same size storage, the integers provide exact numbers evenly spaced between the smallest and largest integer. Floating point values are exact for some values but as there are infinitely many real numbers are only approximations except in special cases. This leads to some differences between math done by hand and math done on the computer.
|
||||
|
||||
### Float64
|
||||
|
||||
`Float64` is the most common type of floating point number, as it is the most supported by the underlying hardware. `Julia` has other floating point types, notably `Float32` and `BigFloat` for certain uses, but our focus here is on 64-bit floating point numbers.
|
||||
|
||||
The double-precision model for floating point numbers has three parts: a sign, an exponent (for a base of $2$), and a significand (in base $2$) representing numbers as $\pm a \cdot 2^n$. The 64 bits are apportioned as follows: $1$ is for the sign, $11$ for the exponent, $52$ for the significand.
|
||||
|
||||
|
||||
The $52$ bits of the significand are used to represent $1.a_1a_2a_3\cdots a_{52}$ in base $2$ or $1 + a_12^{-1} + a_22^{-2} + a_32^{-3} + \cdots a_{52}2^{-52} = b/2^{52}$ for some integer $b$. This means the significand represents a rational number.
|
||||
|
||||
The following shows the bits in the significand for a given number written in the form above:^[The output of `bitstring` is 64 characters. The first is the sign bit, the second through twelfth the exponent, the rest the significand. The notation `[13:end]` is used to return just those for the significand. A value of `[2:12]` would return the bits for the exponent.]
|
||||
|
||||
```{julia}
|
||||
bitstring(1 + 1/2 + 1/4 + 0/8 + 1/16 + 1/32 + 0/64)[13:end] # 1101100…
|
||||
```
|
||||
|
||||
|
||||
The 11 bits for the exponent covers a range from $-1023$ to $1024$ which in base $10$ is around $10^{-308}$ to $10^{308}$.
|
||||
|
||||
Together these can represent exactly any *rational* number of the form $\pm a \cdot 2^b$ where $a$ is a sum of powers of $1/2$ and $b$ is an integer with $1.0 \leq a \leq 1 + (1/2^1) + (1/2^2) + \cdots + (1/2^{52})$ and $-1023 \leq b \leq 1024$.
|
||||
|
||||
@fig-floating-point-concentration shows the possible positive values *were* there only $2$ bits for the exponent (for $-1, 0, 1, 2$) and $2$ bits for the significand ($1 + 0/4 + 0/2$, $1 + 1/4 + 0/2$, $1 + 0/4 + 1/2$, $1 + 1/4 + 1/2$). The main takeaway is that numbers get less concentrated the farther they get from $0$.
|
||||
|
||||
::: {#fig-floating-point-concentration layout-ncol=1}
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
#| fig: floating_point_concentration
|
||||
#| fig-label: floating point concentration
|
||||
using Plots
|
||||
gr()
|
||||
using LaTeXStrings
|
||||
xvals = [0, 1/2, 1, 2, 4, 8]
|
||||
xlabels = [L"0", L"\frac{1}{2}", L"1", L"2", L"4", L"8"]
|
||||
p = plot(; size=(600, 200), xlim = (0, 8.5), ylim=(-1, 1),
|
||||
xaxis=([], false),
|
||||
yaxis=([], false),
|
||||
framestyle=:origin,
|
||||
legend=false,
|
||||
)
|
||||
|
||||
plot!(p, [(0,0), (8.5,0)]; arrow=true, line=(2, :black))
|
||||
xs = range(0, 1 - 1/2, 5)[1:end-1]
|
||||
for (i,xi) in enumerate(xvals[2:end-1])
|
||||
for x in xs
|
||||
x₀ = xi + 2^(i-0)*x/2
|
||||
Δ = 1/20
|
||||
plot!(p, [(x₀, -Δ), (x₀, 2Δ)]; linecolor=:black)
|
||||
end
|
||||
end
|
||||
|
||||
plot!(p; annotations=tuple.(xvals, zero.(xvals), text.(xlabels, :top)))
|
||||
p
|
||||
```
|
||||
|
||||
Toy illustration showing uneven concentration of floating point values. The vertical ticks represent representable floating point values (*were* there only 2 bits (not 52) for the mantissa) and 2 bits (not 11) for the exponent. This leaves a range from a range of $-1/2$ to not quite $8$ being representable *without* using subnormal numbers. When the intervals double in length (from $[2^{i},2^{i+1}]$ to $[2^{i+1}, 2^{i+2}]$) there are the same number of representable floating point values, so the concentration of representable values halves. With more bits there is a higher concentration, but the discrete nature is always present and leads to necessary approximations for modeling most all real numbers.
|
||||
:::
|
||||
|
||||
In addition, there are special bit patterns recognized as `0.0` and even `-0.0`, which is a distinct number. There are also patterns for $+\infty$ (`Inf`) and $-\infty$ (`-Inf`). There are also patterns for `NaN`, or "not a number", a value that is the result of some mathematical operations, such as `0.0 / 0.0`. Finally, there are *subnormal* numbers representing even smaller numbers near $0$ than described above, which are as small as $2^{-1023} \approx 1.11 \cdot 10^{-308}$.
|
||||
|
||||
|
||||
### Scientific notation
|
||||
|
||||
Floating point numbers smaller than $10^{-4}$ or bigger or equal to $10^6$ (in absolute value) are displayed in scientific notation. Internally, most floating point numbers are stored in base $2$ scientific notation as $a \cdot 2^b$ with $a=1.xxx\dots$. But when displayed, numbers are represented in base $10$ and when scientific notation is used the numbers are normalized in the form $a \cdot 10^b$ where $1.0 \leq a < 10$.
|
||||
|
||||
The significand and exponent are separated by the character `e`---which is not the same as the constant $e$---rather denotes a 64-bit number separated into a significand and an exponent by a formatting character. (`Float32` uses an `f` as a separator.)
|
||||
|
||||
Consider these two numbers one close to $0$ one far from $0$:
|
||||
|
||||
```{julia}
|
||||
0.0000000123456789, 123456789.0
|
||||
```
|
||||
|
||||
Their display is subtly different, as only a minus sign after `e` distinguishes them.
|
||||
|
||||
The parser will read in numbers with an `e` in the proper format as though they are scientific notation:
|
||||
|
||||
```{julia}
|
||||
1e8
|
||||
```
|
||||
|
||||
The above creates the same value as `10.0^8`, but not `1e^8` which will error unless a value for `e` has been assigned.
|
||||
|
||||
### Inexactness and consequences
|
||||
|
||||
For numbers not representable in floating point, some rounding must go on to fit the number into a representable floating point value. As such, some computed values are not quite what they would be mathematically:
|
||||
|
||||
```{julia}
|
||||
sqrt(2) * sqrt(2) - 2, sin(1pi)
|
||||
```
|
||||
|
||||
These values are *very* small numbers, but not exactly $0$, as they are mathematically.
|
||||
|
||||
More surprisingly, simple fractions may also lead to mathematically different results:
|
||||
|
||||
---
|
||||
```{julia}
|
||||
1/10 + 2/10 - 3/10
|
||||
```
|
||||
|
||||
This, of course, is due to none of these fractions being of the form $a\cdot 2^b$ for integers $a, b$.
|
||||
|
||||
The only common issue is with powers. We saw this previously when discussing a distinction between `2^64` and `2.0^64`. `Julia` tries to keep a predictable output from the input types (not their values). Here are the two main cases that arise where this can cause unexpected results:
|
||||
|
||||
|
||||
* integer bases and integer exponents can *easily* overflow. Not only `m^n` is always an integer, it is always an integer with a fixed storage size computed from the sizes of `m` and `n`. So the powers can quickly get too big. This can be especially noticeable on older $32$-bit machines, where too big is $2^{32} = 4,294,967,296$. On $64$-bit machines, this limit is present but much bigger.
|
||||
|
||||
|
||||
Rather than give an error though, `Julia` gives seemingly arbitrary answers, as can be seen in this example on a $64$-bit machine:
|
||||
Another surprise: floating point addition is not necessarily associative. That is the property $a + (b+c) = (a+b) + c$ may not hold exactly. For example:
|
||||
|
||||
|
||||
```{julia}
|
||||
2^62, 2^63
|
||||
l2r = (1/10 + 2/10) + 3/10
|
||||
r2l = 1/10 + (2/10 + 3/10)
|
||||
l2r - r2l
|
||||
```
|
||||
|
||||
(They aren't arbitrary, as explained previously.)
|
||||
|
||||
|
||||
This could be worked around, as it is with some programming languages, but it isn't, as it would slow down this basic computation. So, it is up to the user to be aware of cases where their integer values can grow to big. The suggestion is to use floating point numbers in this domain, as they have more room, at the cost of sometimes being approximate values for fairly large values.
|
||||
|
||||
|
||||
* the `sqrt` function will give a domain error for negative values:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| error: true
|
||||
sqrt(-1.0)
|
||||
```
|
||||
|
||||
This is because for real-valued inputs `Julia` expects to return a real-valued output. Of course, this is true in mathematics until the complex numbers are introduced. Similarly in `Julia` - to take square roots of negative numbers, start with complex numbers:
|
||||
|
||||
|
||||
```{julia}
|
||||
sqrt(-1.0 + 0im)
|
||||
```
|
||||
|
||||
* At one point, `Julia` had an issue with a third type of power:
|
||||
|
||||
|
||||
integer bases and negative integer exponents. For example `2^(-1)`. This is now special cased, though only for numeric literals. If `z=-1`, `2^z` will throw a `DomainError`. Historically, the desire to keep a predictable type for the output (integer) led to defining this case as a domain error, but its usefulness led to special casing.
|
||||
|
||||
|
||||
## Additional details.
|
||||
|
||||
|
||||
What follows is only needed for those seeking more background.
|
||||
|
||||
|
||||
Julia has *abstract* number types `Integer`, `Real`, and `Number`. All four types described above are of type `Number`, but `Complex` is not of type `Real`.
|
||||
|
||||
|
||||
However, a specific value is an instance of a *concrete* type. A concrete type will also include information about how the value is stored. For example, the *integer* `1` could be stored using $64$ bits as a signed integers, or, should storage be a concern, as an $8$ bits signed or even unsigned integer, etc.. If storage isn't an issue, but exactness at all scales is, then it can be stored in a manner that allows for the storage to grow using "big" numbers.
|
||||
|
||||
|
||||
These distinctions can be seen in how `Julia` parses these three values:
|
||||
|
||||
|
||||
* `1234567890` will be a $64$-bit integer (on newer machines), `Int64`
|
||||
* `12345678901234567890` will be a $128$ bit integer, `Int128`
|
||||
* `1234567890123456789012345678901234567890` will be a big integer, `BigInt`
|
||||
|
||||
|
||||
Having abstract types allows programmers to write functions that will work over a wide range of input values that are similar, but have different implementation details.
|
||||
|
||||
|
||||
### Integers
|
||||
|
||||
|
||||
Integers are often used casually, as they come about from parsing. As with a calculator, floating point numbers *could* be used for integers, but in `Julia` - and other languages - it proves useful to have numbers known to have *exact* values. In `Julia` there are built-in number types for integers stored in $8$, $16$, $32$, $64$, and $128$ bits and `BigInt`s if the previous aren't large enough. ($8$ bits can hold $8$ binary values representing $1$ of $256=2^8$ possibilities, whereas the larger $128$ bit can hold one of $2^{128}$ possibilities.) Smaller values can be more efficiently used, and this is leveraged at the system level, but not a necessary distinction with calculus where the default size along with an occasional usage of `BigInt` suffice.
|
||||
|
||||
|
||||
### Floating point numbers
|
||||
|
||||
|
||||
[Floating point](http://en.wikipedia.org/wiki/Floating_point) numbers are a computational model for the real numbers. For floating point numbers, $64$ bits are used by default for both $32$- and $64$-bit systems, though other storage sizes can be requested. This gives a large range - but still finite - set of real numbers that can be represented. However, there are infinitely many real numbers just between $0$ and $1$, so there is no chance that all can be represented exactly on the computer with a floating point value. Floating point then is *necessarily* an approximation for all but a subset of the real numbers. Floating point values can be viewed in normalized [scientific notation](http://en.wikipedia.org/wiki/Scientific_notation) as $a\cdot 2^b$ where $a$ is the *significand* and $b$ is the *exponent*. Save for special values, the significand $a$ is normalized to satisfy $1 \leq \lvert a\rvert < 2$, the exponent can be taken to be an integer, possibly negative.
|
||||
|
||||
|
||||
As per IEEE Standard 754, the `Float64` type gives 52 bits to the precision (with an additional implied one), 11 bits to the exponent and the other bit is used to represent the sign. Positive, finite, floating point numbers have a range approximately between $10^{-308}$ and $10^{308}$, as 308 is about $\log_{10} 2^{1023}$. The numbers are not evenly spread out over this range, but, rather, are much more concentrated closer to $0$.
|
||||
|
||||
The use of 32-bit floating point values is common, as some widely used computer chips expect this. These values have a narrower range of possible values.
|
||||
|
||||
:::{.callout-warning}
|
||||
## More on floating point numbers
|
||||
You can discover more about the range of floating point values provided by calling a few different functions.
|
||||
|
||||
* `typemax(0.0)` gives the largest value for the type (`Inf` in this case).
|
||||
* `prevfloat(Inf)` gives the largest finite one, in general `prevfloat` is the next smallest floating point value.
|
||||
|
||||
* `nextfloat(-Inf)`, similarly, gives the smallest finite floating point value, and in general returns the next largest floating point value.
|
||||
* `nextfloat(0.0)` gives the closest positive value to 0.
|
||||
* `eps()` gives the distance to the next floating point number bigger than `1.0`. This is sometimes referred to as machine precision.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
#### Scientific notation
|
||||
|
||||
|
||||
Floating point numbers may print in a familiar manner:
|
||||
|
||||
|
||||
```{julia}
|
||||
x = 1.23
|
||||
```
|
||||
|
||||
or may be represented in scientific notation:
|
||||
|
||||
|
||||
```{julia}
|
||||
6.022 * 10.0^23
|
||||
```
|
||||
|
||||
The special coding `aeb` (or if the exponent is negative `ae-b`) is used to represent the number $a \cdot 10^b$ ($1 \leq a < 10$). This notation can be used directly to specify a floating point value:
|
||||
|
||||
|
||||
```{julia}
|
||||
avogadro = 6.022e23
|
||||
```
|
||||
|
||||
::: {.callout-note}
|
||||
## Not `e`
|
||||
Here `e` is decidedly *not* the Euler number, rather **syntax** to separate the exponent from the mantissa.
|
||||
:::
|
||||
|
||||
The first way of representing this number required using `10.0` and not `10` as the integer power will return an integer and even for 64-bit systems is only valid up to `10^18`. Using scientific notation avoids having to concentrate on such limitations.
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
|
||||
Floating point values in scientific notation will always be normalized. This is easy for the computer to do, but tedious to do by hand. Here we see:
|
||||
|
||||
|
||||
```{julia}
|
||||
4e30 * 3e40
|
||||
```
|
||||
|
||||
```{julia}
|
||||
3e40 / 4e30
|
||||
```
|
||||
|
||||
The power in the first is $71$, not $70 = 30+40$, as the product of $3$ and $4$ as $12$ or `1.2e^1`. (We also see the artifact of `1.2` not being exactly representable in floating point.)
|
||||
|
||||
|
||||
##### Example: 32-bit floating point
|
||||
|
||||
|
||||
In some uses, such as using a GPU, $32$-bit floating point (single precision) is also common. These values may be specified with an `f` in place of the `e` in scientific notation:
|
||||
|
||||
|
||||
```{julia}
|
||||
1.23f0
|
||||
```
|
||||
|
||||
As with the use of `e`, some exponent is needed after the `f`, even if it is `0`.
|
||||
|
||||
|
||||
#### Special values: Inf, -Inf, NaN
|
||||
|
||||
|
||||
The coding of floating point numbers also allows for the special values of `Inf`, `-Inf` to represent positive and negative infinity. As well, a special value `NaN` ("not a number") is used to represent a value that arises when an operation is not closed (e.g., $0.0/0.0$ yields `NaN`). (Technically `NaN` has several possible "values," a point ignored here.) Except for negative bases, the floating point numbers with the addition of `Inf` and `NaN` are closed under the operations `+`, `-`, `*`, `/`, and `^`. Here are some computations that produce `NaN`:
|
||||
|
||||
|
||||
```{julia}
|
||||
0/0, Inf/Inf, Inf - Inf, 0 * Inf
|
||||
```
|
||||
|
||||
Whereas, these produce an infinity
|
||||
|
||||
|
||||
```{julia}
|
||||
1/0, Inf + Inf, 1 * Inf
|
||||
```
|
||||
|
||||
Finally, these are mathematically undefined, but still yield a finite value with `Julia`:
|
||||
|
||||
|
||||
```{julia}
|
||||
0^0, Inf^0
|
||||
```
|
||||
|
||||
#### Floating point numbers and real numbers
|
||||
|
||||
|
||||
Floating point numbers are an abstraction for the real numbers. For the most part this abstraction works in the background, though there are cases where one needs to have it in mind. Here are a few:
|
||||
|
||||
|
||||
* For real and rational numbers, between any two numbers $a < b$, there is another real number in between. This is not so for floating point numbers which have a finite precision. (Julia has some functions for working with this distinction.)
|
||||
* Floating point numbers are approximations for most values, even simple rational ones like $1/3$. This leads to oddities such as this value not being $0$:
|
||||
|
||||
|
||||
```{julia}
|
||||
sqrt(2)*sqrt(2) - 2
|
||||
```
|
||||
|
||||
It is no surprise that an irrational number, like $\sqrt{2}$, can't be represented **exactly** within floating point, but it is perhaps surprising that simple numbers can not be, so $1/3$, $1/5$, $\dots$ are approximated. Here is a surprising-at-first consequence:
|
||||
|
||||
|
||||
```{julia}
|
||||
1/10 + 2/10 == 3/10
|
||||
```
|
||||
|
||||
That is adding `1/10` and `2/10` is not exactly `3/10`, as expected mathematically. Such differences are usually very small and are generally attributed to rounding error. The user needs to be mindful when testing for equality, as is done above with the `==` operator.
|
||||
|
||||
|
||||
* Floating point addition is not necessarily associative, that is the property $a + (b+c) = (a+b) + c$ may not hold exactly. For example:
|
||||
|
||||
|
||||
```{julia}
|
||||
1/10 + (2/10 + 3/10) == (1/10 + 2/10) + 3/10
|
||||
```
|
||||
|
||||
* Mathematically, for real numbers, subtraction of similar-sized numbers is not exceptional, for example $1 - \cos(x)$ is positive if $0 < x < \pi/2$, say. This will not be the case for floating point values. If $x$ is close enough to $0$, then $\cos(x)$ and $1$ will be so close, that they will be represented by the same floating point value, `1.0`, so the difference will be zero:
|
||||
One other surprise. Mathematically, for real numbers, subtraction of similar-sized numbers is not exceptional, for example $1 - \cos(x)$ is positive if $0 < x < \pi/2$, say. This will not be the case for floating point values. If $x$ is close enough to $0$, then $\cos(x)$ and $1$ will be so close, that they will be represented by the same floating point value, `1.0`, so the difference will be zero:
|
||||
|
||||
|
||||
```{julia}
|
||||
1.0 - cos(1e-8)
|
||||
```
|
||||
|
||||
### Rational numbers
|
||||
## Rational numbers
|
||||
|
||||
|
||||
Rational numbers can be used when the exactness of the number is more important than the speed or wider range of values offered by floating point numbers. In `Julia` a rational number is comprised of a numerator and a denominator, each an integer of the same type, and reduced to lowest terms. The operations of addition, subtraction, multiplication, and division will keep their answers as rational numbers. As well, raising a rational number to an integer value will produce a rational number.
|
||||
@@ -346,25 +259,27 @@ For powers, a non-integer exponent is converted to floating point, so this opera
|
||||
(1//2)^(1//2) # the first parentheses are necessary as `^` will be evaluated before `//`.
|
||||
```
|
||||
|
||||
##### Example: different types of real numbers
|
||||
----
|
||||
|
||||
@tbl-real-number-types compares different number types for storing a real number. The "closed under" column indicates which operations will return the same type as the inputs.
|
||||
|
||||
|
||||
This table shows what attributes are implemented for the different types.
|
||||
::: {#tbl-real-number-types}
|
||||
|
||||
| Attributes | Integer | Rational | FloatingPoint |
|
||||
|:------------|:---------|:---------|:--------------|
|
||||
| construction| 1 | 1//1 |1.0 |
|
||||
| exact | true | true |not always |
|
||||
| wide range | false | false |true |
|
||||
| has infinity| false | false |true |
|
||||
| has -0 | false | false |true |
|
||||
| fast | true | false |true |
|
||||
| closed under| `+`, `-`, `*`, `^` (non-negative exponent)| `+`, `-`, `*`, `/` (non zero denominator),`^` (integer power) | `+`, `-`, `*`, `/` (possibly `NaN`, `Inf`),`^` (non-negative base) |
|
||||
|
||||
:::
|
||||
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
using DataFrames
|
||||
attributes = ["construction", "exact", "wide range", "has infinity", "has `-0`", "fast", "closed under"]
|
||||
integer = [q"1", "true", "false", "false", "false", "true", "`+`, `-`, `*`, `^` (non-negative exponent)"]
|
||||
rational = ["`1//1`", "true", "false", "false", "false", "false", "`+`, `-`, `*`, `/` (non zero denominator),`^` (integer power)"]
|
||||
float = [q"1.0", "not usually", "true", "true", "true", "true", "`+`, `-`, `*`, `/` (possibly `NaN`, `Inf`),`^` (non-negative base)"]
|
||||
d = DataFrame(Attributes=attributes, Integer=integer, Rational=rational, FloatingPoint=float)
|
||||
table(d)
|
||||
```
|
||||
|
||||
### Complex numbers
|
||||
|
||||
## Complex numbers
|
||||
|
||||
Complex numbers in `Julia` are stored as two numbers, a real and imaginary part, each some type of `Real` number. The special constant `im` is used to represent $i=\sqrt{-1}$. This makes the construction of complex numbers fairly standard:
|
||||
|
||||
@@ -376,7 +291,7 @@ Complex numbers in `Julia` are stored as two numbers, a real and imaginary part,
|
||||
(These two aren't exactly the same, the `3` is promoted from an integer to a float to match the `4.0`. Each of the components must be of the same type of number.)
|
||||
|
||||
|
||||
Mathematically, complex numbers are needed so that certain equations can be satisfied. For example $x^2 = -2$ has solutions $-\sqrt{2}i$ and $\sqrt{2}i$ over the complex numbers. Finding this in `Julia` requires some attention, as we have both `sqrt(-2)` and `sqrt(-2.0)` throwing a `DomainError`, as the `sqrt` function expects non-negative real arguments. However first creating a complex number does work:
|
||||
Mathematically, complex numbers are needed so that certain equations can be satisfied. For example $x^2 = -2$ has solutions $-\sqrt{2}i$ and $\sqrt{2}i$ over the complex numbers. Finding this in `Julia` requires some attention, as we have both `sqrt(-2)` and `sqrt(-2.0)` throwing a `DomainError`, as the `sqrt` function expects non-negative real arguments. However first creating a complex number and then taking a square root does work:
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -397,14 +312,20 @@ discr = b^2 - 4a*c
|
||||
|
||||
When learning calculus, the only common usage of complex numbers arises when solving polynomial equations for roots, or zeros, though they are very important for subsequent work using the concepts of calculus.
|
||||
|
||||
## Irrational numbers
|
||||
|
||||
:::{.callout-note}
|
||||
## Note
|
||||
Though complex numbers are stored as pairs of numbers, the imaginary unit, `im`, is of type `Complex{Bool}`, a type that can be promoted to more specific types when `im` is used with different number types.
|
||||
`Julia` has a a few mathematical constants that are stored with a special type `Irrational`. One such value is `pi`. There are others in the `Base.MathConstants` module, and an external package `IrrationalConstants.jl`.
|
||||
|
||||
:::
|
||||
`Irrational` values may have special methods defined for them which can lead to subtle differences, such as:
|
||||
|
||||
```{julia}
|
||||
sin(pi), sin(2pi)
|
||||
```
|
||||
|
||||
In computing the product `2pi` first the two values are promoted to `Float64` and then multiplied, leaving a floating-point approximation of $2\pi$ for `sin` to evaluate.
|
||||
|
||||
## Other types of data: strings and symbols
|
||||
|
||||
### Strings and symbols
|
||||
|
||||
For text, `Julia` has a `String` type. When double quotes are used to specify a string, the parser creates this type:
|
||||
|
||||
@@ -436,76 +357,12 @@ Strings are comprised of *characters* which can be produced directly using *sing
|
||||
'c'
|
||||
```
|
||||
|
||||
We won't use these.
|
||||
We won't use characters in these notes.
|
||||
|
||||
Finally, `Julia` has *symbols* which are *interned* strings which are used as identifiers. Symbols are used for advanced programming techniques; we will only see them as shortcuts to specify plotting arguments.
|
||||
|
||||
## Type stability
|
||||
|
||||
|
||||
One design priority of `Julia` is that it should be fast. How can `Julia` do this? In a simple model, `Julia` is an interface between the user and the computer's processor(s). Processors consume a set of instructions, the user issues a set of commands. `Julia` is in charge of the translation between the two. Ultimately `Julia` calls a compiler to create the instructions. A basic premise is the shorter the instructions, the faster they are to process. Shorter instructions can come about by being more explicit about what types of values the instructions concern. Explicitness means, there is no need to reason about what a value can be. When `Julia` can reason about the type of value involved without having to reason about the values themselves, it can work with the compiler to produce shorter lists of instructions.
|
||||
|
||||
|
||||
So knowing the type of the output of a function based only on the type of the inputs can be a big advantage. In `Julia` this is known as *type stability*. In the standard `Julia` library, this is a primary design consideration.
|
||||
|
||||
|
||||
##### Example: closure
|
||||
|
||||
|
||||
To motivate this a bit, we discuss how mathematics can be shaped by a desire to stick to simple ideas. A desirable algebraic property of a set of numbers and an operation is *closure*. That is, if one takes an operation like `+` and then uses it to add two numbers in a set, will that result also be in the set? If this is so for any pair of numbers, then the set is closed with respect to the operation addition.
|
||||
|
||||
|
||||
Lets suppose we start with the *natural numbers*: $1,2, \dots$. Natural, in that we can easily represent small values in terms of fingers. This set is closed under addition - as a child learns when counting using their fingers. However, if we started with the odd natural numbers, this set would *not* be closed under addition - $3+3=6$.
|
||||
|
||||
|
||||
The natural numbers are not all the numbers we need, as once a desire for subtraction is included, we find the set isn't closed. There isn't a $0$, needed as $n-n=0$ and there aren't negative numbers. The set of integers are needed for closure under addition and subtraction.
|
||||
|
||||
|
||||
The integers are also closed under multiplication, which for integer values can be seen as just regrouping into longer additions.
|
||||
|
||||
|
||||
However, the integers are not closed under division - even if you put aside the pesky issue of dividing by $0$. For that, the rational numbers must be introduced. So aside from division by $0$, the rationals are closed under addition, subtraction, multiplication, and division. There is one more fundamental operation though, powers.
|
||||
|
||||
|
||||
Powers are defined for positive integers in a simple enough manner
|
||||
|
||||
|
||||
$$
|
||||
a^n=a \cdot a \cdot a \cdots a \text{ (n times); } a, n \text{ are integers } n \text{ is positive}.
|
||||
$$
|
||||
|
||||
We can define $a^0$ to be $1$, except for the special case of $0^0$, which is left undefined mathematically (though it is also defined as `1` within `Julia`). We can extend the above to include negative values of $a$, but what about negative values of $n$? We can't say the integers are closed under powers, as the definition consistent with the rules that $a^{(-n)} = 1/a^n$ requires rational numbers to be defined.
|
||||
|
||||
|
||||
Well, in the above `a` could be a rational number, is `a^n` closed for rational numbers? No again. Though it is fine for $n$ as an integer (save the odd case of $0$, simple definitions like $2^{1/2}$ are not answered within the rationals. For this, we need to introduce the *real* numbers. It is mentioned that [Aristotle](http://tinyurl.com/bpqbkap) hinted at the irrationality of the square root of $2$. To define terms like $a^{1/n}$ for integer values $a,n > 0$ a reference to a solution to an equation $x^n-a$ is used. Such solutions require the irrational numbers to have solutions in general. Hence the need for the real numbers (well, algebraic numbers at least, though once the exponent is no longer a rational number, the full set of real numbers are needed.)
|
||||
|
||||
|
||||
So, save the pesky cases, the real numbers will be closed under addition, subtraction, multiplication, division, and powers - provided the base is non-negative.
|
||||
|
||||
|
||||
Finally for that last case, the complex numbers are introduced to give an answer to $\sqrt{-1}$.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
How does this apply with `Julia`?
|
||||
|
||||
|
||||
The point is, if we restrict our set of inputs, we can get more precise values for the output of basic operations, but to get more general inputs we need to have bigger output sets.
|
||||
|
||||
|
||||
A similar thing happens in `Julia`. For addition say, the addition of two integers of the same type will be an integer of that type. This speed consideration is not solely for type stability, but also to avoid checking for overflow.
|
||||
|
||||
|
||||
Another example, the division of two integers will always be a number of the same type - floating point, as that is the only type that ensures the answer will always fit within. (The explicit use of rationals notwithstanding.) So even if two integers are the input and their answer *could* be an integer, in `Julia` it will be a floating point number, (cf. `2/1`).
|
||||
|
||||
|
||||
Hopefully this helps explain the subtle issues around powers: in `Julia` an integer raised to an integer should be an integer, for speed, though certain cases are special cased, like `2^(-1)`. However since a real number raised to a real number makes sense always when the base is non-negative, as long as real numbers are used as outputs, the expressions `2.0^(-1)` and `2^(-1.0)` are computed and real numbers (floating points) are returned. For type stability, even though $2.0^1$ could be an integer, a floating point answer is returned.
|
||||
|
||||
|
||||
As for negative bases, `Julia` could always return complex numbers, but in addition to this being slower, it would be irksome to users. So user's must opt in. Hence `sqrt(-1.0)` will be an error, but the more explicit - but mathematically equivalent - `sqrt(-1.0 + 0im)` will not be a domain error, but rather a complex value will be returned.
|
||||
|
||||
|
||||
## Questions
|
||||
|
||||
@@ -667,7 +524,7 @@ yesnoq(false)
|
||||
|
||||
###### Question
|
||||
|
||||
In [NewScientist](https://www.newscientist.com/article/2112537-smallest-sliver-of-time-yet-measured-sees-electrons-fleeing-atom/) we learn "For the first time, physicists have measured changes in an atom to the level of zeptoseconds, or trillionths of a billionth of a second – the smallest division of time yet observed."
|
||||
In [NewScientist](https://www.newscientist.com/article/2112537-smallest-sliver-of-time-yet-measured-sees-electrons-fleeing-atom/) we learn "For the first time, physicists have measured changes in an atom to the level of zeptoseconds, or trillionths of a billionth of a second---the smallest division of time yet observed."
|
||||
|
||||
That is
|
||||
|
||||
@@ -683,3 +540,156 @@ as = ["1/10^21", "1e-21"]
|
||||
explanation = "The scientific notation is correct. Due to integer overflow `10^21` is not the same number as `10.0^21`."
|
||||
buttonq(as, 2; explanation)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
Signed integers are stored on a computer in a special manner. We will see with 8 bit integers, formed by `Int8`. Eight bit means only 8 0's or 1's are used to store a given number. This is a useful format for storing many small integers but for this example, useful as we can more easily track the values.
|
||||
|
||||
The first bit is a sign bit. Based on these two outputs, can you guess how that works:
|
||||
|
||||
```{julia}
|
||||
bitstring(Int8(-1)), bitstring(Int8(1))
|
||||
```
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["The first digit is `1` for positive numbers, `0` for negative numbers",
|
||||
"The first digit is `0` for positive numbers, `1` for negative numbers"]
|
||||
answer = 2
|
||||
buttonq(choices, answer)
|
||||
```
|
||||
|
||||
Positive numbers and negative numbers are stored a bit differently. Positive numbers just use binary: $a_0 \cdot 2^0 + a_1 \cdot 2^1 + a_2 \cdot 2^2 + \cdots a_7 \cdot 2^7$. The number $27$ is $1 + 2 + 8 + 16$. so have $a_0 = a_1 = a_3 = a_4 = 1$, the others are $0$. The bitstring shows:
|
||||
|
||||
```{julia}
|
||||
bitstring(Int8(27))
|
||||
```
|
||||
|
||||
Which bit pattern is used?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["`sign bit | a₀ | a₁ | a₂ | a₃ | a₄ | a₅ | a₆ | a₇`",
|
||||
"`sign bit | a₇ | a₆ | a₅ | a₄ | a₃ | a₂ | a₁ | a₀`"]
|
||||
answer = 2
|
||||
buttonq(choices, answer)
|
||||
```
|
||||
|
||||
Negative numbers are stored using *two's complement* format:
|
||||
|
||||
* represent the positive number
|
||||
* flip `0` to `1`; `1` to `0`
|
||||
* add `1` to the value (long addition with carrying)
|
||||
|
||||
For $-27$ we have
|
||||
|
||||
* first $27$: `00011011`
|
||||
* then flip each: `11100100`
|
||||
* finally add $1$: `11100101`
|
||||
|
||||
```{julia}
|
||||
bitstring(Int8(-27))
|
||||
```
|
||||
|
||||
The largest positive number is $127$ for 8-bits and is represented by `01111111`. What is the bit pattern of $-127$?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["`01111111 => 10000000 => 10000001`",
|
||||
"`01111111 => 10000000 => 100000000`"]
|
||||
answer = 1
|
||||
explanation = "Add the `1` on the right side, not the left side"
|
||||
buttonq(choices, answer; explanation)
|
||||
```
|
||||
|
||||
The smallest negative number is $-128$. Why?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["Subtracting 1 from `10000001` leaves `10000000` which is the smallest negative number",
|
||||
"Tricky, the smallest negative number is ``-127``"
|
||||
]
|
||||
answer = 1
|
||||
buttonq(choices, answer)
|
||||
```
|
||||
|
||||
Why all this fuss? Couldn't there be an easier way?
|
||||
|
||||
This storage has a big advantage when adding numbers. Let's look at adding $-5$ to $6$. we have:
|
||||
|
||||
```
|
||||
-5 => 11111011
|
||||
6 => 00000110
|
||||
--------
|
||||
100000001 => 00000001
|
||||
```
|
||||
|
||||
The addition is done by carrying a `1` across and then *dropping* the 9th number when there is such a carry. This leaves the representation for what number?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["`1`", "`-1`", "`0`"]
|
||||
answer = 1
|
||||
buttonq(choices, answer)
|
||||
```
|
||||
|
||||
|
||||
The largest *positive* number that can be represented is $2^0 + 2^1 + 2^2 + \cdots + 2^7$, where $7$ is the number of bits minus $1$. The representation is `01111111`. What happens if we add `1` to this number?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["The value after carrying is `100000000`. After dropping the 9th number, we get `00000000` or zero",
|
||||
"The value after carrying is `10000000` or ``-128``"]
|
||||
answer = 2
|
||||
explanation = "Addition wraps around from the largest to the smallest and goes from there."
|
||||
buttonq(choices, answer)
|
||||
```
|
||||
|
||||
The largest possible number for a type is returned by `typemax`. For `Int64` (just `Int` on most systems) what is the largest number?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
numericq(typemax(Int64))
|
||||
```
|
||||
|
||||
|
||||
###### Question
|
||||
|
||||
The `Float64` type uses $11$ bits for an exponent (base $2$) between $-1023$ and $1024$. We can see how these are stored as follows:
|
||||
|
||||
```{julia}
|
||||
bitstring(2.0^(-1023))[2:12], bitstring(2.0^(1024))[2:12]
|
||||
```
|
||||
|
||||
This is the full range of values. However the values are *shifted* with $0$ representing $-1023$ and $x$ representing $1024$. The value $x$ is can be found from:
|
||||
|
||||
```{julia}
|
||||
#| eval: false
|
||||
2^0 + 2^1 + 2^2 + 2^3 + 2^4 + 2^5 + 2^6 + 2^7 + 2^8 + 2^9 + 2^10
|
||||
```
|
||||
|
||||
What is the value of $x$?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
val = sum(2^i for i in 0:10)
|
||||
numericq(val)
|
||||
```
|
||||
|
||||
The value `1023` is called a bias. The exponent is coded as the binary value as a positve integer minus $1023$. A bias is used, and not the two's complement format, as storage with a bias makes multiplying by powers of $2$ as easy as shifting the bits.
|
||||
|
||||
To find the storage for, say, $2^4 + 2^2 + 2^0$ or `00000010101` we would add `1023` or `01111111111` and see:
|
||||
|
||||
```
|
||||
00000010101
|
||||
+ 01111111111
|
||||
-----------
|
||||
10000010100
|
||||
```
|
||||
|
||||
Which we can see:
|
||||
|
||||
```{julia}
|
||||
bitstring(2.0^(2^4 + 2^2 + 2^0))[2:12]
|
||||
```
|
||||
+71
-89
@@ -63,68 +63,53 @@ Rather than express sequences by the $a_0$, $h$, and $n$, `Julia` uses the start
|
||||
|
||||
|
||||
```{julia}
|
||||
1:10
|
||||
1:5
|
||||
```
|
||||
|
||||
But wait, nothing different printed? This is because `1:10` is efficiently stored. Basically, a recipe to generate the next number from the previous number is created and `1:10` just stores the start and end points (the step size is implicit in how this is stored) and that recipe is used to generate the set of all values. To expand the values, you have to ask for them to be `collect`ed (though this typically isn't needed in practice, as values are usually *iterated* over):
|
||||
But wait, nothing different printed? This is because `1:5` is efficiently stored. Basically, a recipe to generate the next number from the previous number is created and `1:5` just stores the start and end points (the step size is implicit in how this is stored) and that recipe is used to generate the set of all values. To expand the values, you have to ask for them to be `collect`ed (though this typically isn't needed in practice, as values are usually *iterated* over):
|
||||
|
||||
|
||||
```{julia}
|
||||
collect(1:10)
|
||||
collect(1:5)
|
||||
```
|
||||
|
||||
When a non-default step size is needed, it goes in the middle, as in `a:h:b`. For example, counting by sevens from $1$ to $50$ is achieved by:
|
||||
When a non-default step size is needed, it goes in the middle, as in `a:h:b`. For example, counting by sevens from $1$ to 29 is achieved by:
|
||||
|
||||
|
||||
```{julia}
|
||||
collect(1:7:50)
|
||||
collect(1:7:29)
|
||||
```
|
||||
|
||||
Or counting down from 100:
|
||||
|
||||
|
||||
```{julia}
|
||||
collect(100:-7:1)
|
||||
collect(100:-7:70)
|
||||
```
|
||||
|
||||
In this last example, we said end with $1$, but it ended with $2$. The ending value in the range is a suggestion to go up to, but not exceed. Negative values for `h` are used to make decreasing sequences.
|
||||
In this last example, we said end with $70$, but it ended with $72$. The ending value in the range is a suggestion to go up to, but not exceed. Negative values for `h` are used to make decreasing sequences.
|
||||
|
||||
|
||||
### The range function
|
||||
|
||||
|
||||
For generating points to make graphs, a natural set of points to specify is $n$ evenly spaced points between $a$ and $b$. We can mimic creating this set with the range operation by solving for the correct step size. We have $a_0=a$ and $a_0 + (n-1) \cdot h = b$. (Why $n-1$ and not $n$?) Solving yields $h = (b-a)/(n-1)$. To be concrete we might ask for $9$ points between $-1$ and $1$:
|
||||
For generating points to make graphs, a natural set of points to specify is $n$ *evenly* spaced points between $a$ and $b$. We can mimic creating this set with the range operation by solving for the correct step size. We have $a_0=a$ and $a_0 + (n-1) \cdot h = b$. (Why $n-1$ and not $n$?) Solving yields $h = (b-a)/(n-1)$. To be concrete we might ask for $5$ points between $-1$ and $1$:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
a, b, n = -1, 1, 9
|
||||
a, b, n = -1, 1, 5
|
||||
h = (b-a)/(n-1)
|
||||
collect(a:h:b)
|
||||
```
|
||||
|
||||
Pretty neat. If we were doing this many times - such as once per plot - we'd want to encapsulate this into a function, for example using a comprehension:
|
||||
Now, our recipe is straightforward, but only because it avoids somethings. Look at something simple:
|
||||
|
||||
|
||||
```{julia}
|
||||
function evenly_spaced(a, b, n)
|
||||
h = (b-a)/(n-1)
|
||||
[a + i*h for i in 0:n-1]
|
||||
end
|
||||
```
|
||||
|
||||
Great, let's try it out:
|
||||
|
||||
|
||||
```{julia}
|
||||
evenly_spaced(0, 2pi, 5)
|
||||
```
|
||||
|
||||
Now, our implementation was straightforward, but only because it avoids somethings. Look at something simple:
|
||||
|
||||
|
||||
```{julia}
|
||||
evenly_spaced(1/5, 3/5, 3)
|
||||
a, b, n = 1/5, 3/5, 3
|
||||
h = (b-a)/(n-1)
|
||||
collect(a:h:b)
|
||||
```
|
||||
|
||||
It seems to work as expected. But looking just at the algorithm it isn't quite so clear:
|
||||
@@ -137,7 +122,7 @@ It seems to work as expected. But looking just at the algorithm it isn't quite s
|
||||
Floating point roundoff leads to the last value *exceeding* `0.6`, so should it be included? Well, here it is pretty clear it *should* be, but better to have something programmed that hits both `a` and `b` and adjusts `h` accordingly. Something which isn't subject to the vagaries of `(3/5 - 1/5)/2` not being `0.2`.
|
||||
|
||||
|
||||
Enter the base function `range` which solves this seemingly simple - but not really - task. It can use `a`, `b`, and `n`. Like the range operation, this function returns a generator which can be collected to realize the values.
|
||||
Enter the base function `range` which solves this seemingly simple---but not really---task. It can use `a`, `b`, and `n`. Like the range operation, this function returns a generator which can be collected to realize the values.
|
||||
|
||||
|
||||
The number of points is specified as a third argument (though keyword arguments can be given):
|
||||
@@ -153,11 +138,25 @@ There is also the `LinRange(a, b, n)` function which can be more performant than
|
||||
|
||||
:::
|
||||
|
||||
|
||||
## Modifying sequences
|
||||
|
||||
|
||||
Now we concentrate on some more general styles to modify a sequence to produce a new sequence.
|
||||
|
||||
### The repeat function
|
||||
|
||||
The `repeat` function is used to repeat entries of an array a certain number of times. For a vector, there are two different patterns of interest: repeating the first element $k$ times, then the second, then the third etc. Or repeating the entire vector a number of times. The first one repeats *inner* values, the second *outer*. We illustrate with two examples:
|
||||
|
||||
```{julia}
|
||||
repeat(1:3, inner=2) # repeat 1 twice, then 2 twice, then 3 twice
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```{julia}
|
||||
repeat(1:3, outer=2) # repeat 1:3 twice
|
||||
```
|
||||
|
||||
### Filtering
|
||||
|
||||
@@ -167,28 +166,28 @@ The act of throwing out elements of a collection based on some condition is call
|
||||
For example, another way to get the values between $0$ and $100$ that are multiples of $7$ is to start with all $101$ values and throw out those that don't match. To check if a number is divisible by $7$, we could use the `rem` function. It gives the remainder upon division. Multiples of `7` match `rem(m, 7) == 0`. Checking for divisibility by seven is unusual enough there is nothing built in for that, but checking for division by $2$ is common, and for that, there is a built-in function `iseven`.
|
||||
|
||||
|
||||
The `filter` function does this in `Julia`; the basic syntax being `filter(predicate_function, collection)`. The "`predicate_function`" is one that returns either `true` or `false`, such as `iseven`. The output of `filter` consists of the new collection of values - those where the predicate returns `true`.
|
||||
The `filter` function does this in `Julia`; the basic syntax being `filter(predicate_function, collection)`. The "`predicate_function`" is one that returns either `true` or `false`, such as `iseven`. The output of `filter` consists of the new collection of values---those in the collection for which the predicate returns `true`.
|
||||
|
||||
|
||||
To see it used, lets start with the numbers between `0` and `25` (inclusive) and filter out those that are even:
|
||||
To see it used, lets start with the numbers between `0` and `9` (inclusive) and filter out those that are even:
|
||||
|
||||
|
||||
```{julia}
|
||||
filter(iseven, 0:25)
|
||||
filter(iseven, 0:9)
|
||||
```
|
||||
|
||||
To get the numbers between $1$ and $100$ that are divisible by $7$ requires us to write a function akin to `iseven`, which isn't hard (e.g., `is_seven(x) = x%7 == 0` or if being fancy `Base.Fix2(iszero∘rem, 7)`), but isn't something we continue with just yet.
|
||||
|
||||
|
||||
For another example, here is an inefficient way to list the prime numbers between $100$ and $200$. This uses the `isprime` function from the `Primes` package
|
||||
For another example, here is an inefficient way to list the prime numbers between $100$ and $130$. This uses the `isprime` function from the `Primes` package
|
||||
|
||||
|
||||
```{julia}
|
||||
using Primes
|
||||
using Primes: isprime
|
||||
```
|
||||
|
||||
```{julia}
|
||||
filter(isprime, 100:200)
|
||||
filter(isprime, 100:130)
|
||||
```
|
||||
|
||||
Illustrating `filter` at this point is mainly a motivation to illustrate that we can start with a regular set of numbers and then modify or filter them. The function takes on more value once we discuss how to write predicate functions.
|
||||
@@ -202,7 +201,7 @@ Let's return to the case of the set of even numbers between $0$ and $100$. We ha
|
||||
|
||||
* The collection of numbers $0, 2, 4, 6 \dots, 100$, or the arithmetic sequence with step size $2$, which is returned by `0:2:100`.
|
||||
* The numbers between $0$ and $100$ that are even, that is `filter(iseven, 0:100)`.
|
||||
* The set of numbers $\{2k: k=0, \dots, 50\}$.
|
||||
* The set of numbers $\{2k: k=0, \dots, 5\}$.
|
||||
|
||||
|
||||
While `Julia` has a special type for dealing with sets, we will use a vector for such a set. (Unlike a set, vectors can have repeated values, but, as vectors are more widely used, we demonstrate them.) Vectors are described more fully in a previous section, but as a reminder, vectors are constructed using square brackets: `[]` (a special syntax for [concatenation](http://docs.julialang.org/en/latest/manual/arrays/#concatenation)). Square brackets are used in different contexts within `Julia`, in this case we use them to create a *collection*. If we separate single values in our collection by commas (or semicolons), we will create a vector:
|
||||
@@ -215,48 +214,45 @@ x = [0, 2, 4, 6, 8, 10]
|
||||
That is of course only part of the set of even numbers we want. Creating more might be tedious were we to type them all out, as above. In such cases, it is best to *generate* the values.
|
||||
|
||||
|
||||
For this simple case, a range can be used, but more generally a [comprehension](https://docs.julialang.org/en/v1/manual/arrays/#man-comprehensions) provides this ability using a construct that closely mirrors a set definition, such as $\{2k: k=0, \dots, 50\}$. The simplest use of a comprehension takes this form (as we described in the section on vectors):
|
||||
For this simple case, a range can be used, but more generally a [comprehension](https://docs.julialang.org/en/v1/manual/arrays/#man-comprehensions) provides this ability using a construct that closely mirrors a set definition, such as $\{2k: k=0, \dots, 5\}$. The simplest use of a comprehension takes this form (as we described in the section on vectors):
|
||||
|
||||
|
||||
`[expr for variable in collection]`
|
||||
|
||||
|
||||
The expression typically involves the variable specified after the keyword `for`. The collection can be a range, a vector, or many other items that are *iterable*. Here is how the mathematical set $\{2k: k=0, \dots, 50\}$ may be generated by a comprehension:
|
||||
The expression typically involves the variable specified after the keyword `for`. The collection can be a range, a vector, or many other items that are *iterable*. (We previously discussed the `map` and broadcasting alternatives to a comprehension, but those are most useful once we have discussed user-defined functions.)
|
||||
|
||||
|
||||
|
||||
Here is how the mathematical set $\{2k: k=0, \dots, 5\}$ may be generated by a comprehension:
|
||||
|
||||
|
||||
```{julia}
|
||||
[2k for k in 0:50]
|
||||
[2k for k in 0:5]
|
||||
```
|
||||
|
||||
The expression is `2k`, the variable `k`, and the collection is the range of values, `0:50`. The syntax is basically identical to how the math expression is typically read aloud.
|
||||
The expression is `2k`, the variable `k`, and the collection is the range of values, `0:5`. The syntax is basically identical to how the math expression is typically read aloud.
|
||||
|
||||
|
||||
For some other examples, here is how we can create the first $10$ numbers divisible by $7$:
|
||||
For some other examples, here is how we can create the first 5 numbers divisible by $7$:
|
||||
|
||||
|
||||
```{julia}
|
||||
[7k for k in 1:10]
|
||||
[7k for k in 1:5]
|
||||
```
|
||||
|
||||
Here is how we can square the numbers between $1$ and $10$:
|
||||
Here is how we can square the numbers between $1$ and 5:
|
||||
|
||||
|
||||
```{julia}
|
||||
[x^2 for x in 1:10]
|
||||
[x^2 for x in 1:5]
|
||||
```
|
||||
|
||||
To generate other progressions, such as powers of $2$, we could do:
|
||||
To generate other progressions, such as decreasing powers of $2$, we could do:
|
||||
|
||||
|
||||
```{julia}
|
||||
[2^i for i in 1:10]
|
||||
```
|
||||
|
||||
Here are decreasing powers of $2$:
|
||||
|
||||
|
||||
```{julia}
|
||||
[1/2^i for i in 1:10]
|
||||
[1/2^i for i in 1:5]
|
||||
```
|
||||
|
||||
Sometimes, the comprehension does not produce the type of output that may be expected. This is related to `Julia`'s more limited abilities to infer types at the command line. If the output type is important, the extra prefix of `T[]` can be used, where `T` is the desired type.
|
||||
@@ -269,14 +265,14 @@ A typical pattern would be to generate a collection of numbers and then apply a
|
||||
|
||||
|
||||
```{julia}
|
||||
sum([2^i for i in 1:10])
|
||||
sum([2^i for i in 1:5])
|
||||
```
|
||||
|
||||
Conceptually this is easy to understand: one step generates the numbers, the other adds them up. Computationally it is a bit inefficient. The generator syntax allows this type of task to be done more efficiently. To use this syntax, we just need to drop the `[]`:
|
||||
|
||||
|
||||
```{julia}
|
||||
sum(2^i for i in 1:10)
|
||||
sum(2^i for i in 1:5)
|
||||
```
|
||||
|
||||
The difference being no intermediate object is created to store the collection of all values specified by the generator. Not all functions allow generators as arguments, but most common reductions do.
|
||||
@@ -314,7 +310,7 @@ The same `if` can be used in a comprehension. For example, this is an alternativ
|
||||
|
||||
|
||||
```{julia}
|
||||
[k for k in 1:100 if rem(k,7) == 0]
|
||||
[k for k in 71:100 if rem(k,7) == 0]
|
||||
```
|
||||
|
||||
#### Example: Making change
|
||||
@@ -402,11 +398,11 @@ If the command is run again, it is almost certain that a different value will be
|
||||
rand()
|
||||
```
|
||||
|
||||
This call will return a vector of $10$ such random numbers:
|
||||
This call will return a vector of 4 such random numbers:
|
||||
|
||||
|
||||
```{julia}
|
||||
rand(10)
|
||||
rand(4)
|
||||
```
|
||||
|
||||
The `rand` function is easy to use. The only common source of confusion is the subtle distinction between `rand()` and `rand(1)`, as the latter is a vector of $1$ random number and the former just $1$ random number.
|
||||
@@ -544,23 +540,6 @@ answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
An arithmetic sequence ($a_0$, $a_1 = a_0 + h$, $a_2=a_0 + 2h, \dots,$ $a_n=a_0 + n\cdot h$) is specified with a starting point ($a_0$), a step size ($h$), and a number of points $(n+1)$. This is not the case with the colon constructor which take a starting point, a step size, and a suggested last value. This is not the case a with the default for the `range` function, with signature `range(start, stop, length)`. However, the documentation for `range` shows that indeed the three values ($a_0$, $h$, and $n$) can be passed in. Which signature (from the docs) would allow this:
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = [
|
||||
"`range(start, stop, length)`",
|
||||
"`range(start, stop; length, step)`",
|
||||
"`range(start; length, stop, step)`",
|
||||
"`range(;start, length, stop, step)`"]
|
||||
answer = 3
|
||||
explanation = """
|
||||
This is a somewhat vague question, but the use of `range(a0; length=n+1, step=h)` will produce the arithmetic sequence with this parameterization.
|
||||
"""
|
||||
buttonq(choices, answer; explanation)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
@@ -708,28 +687,22 @@ Credit card numbers have a check digit to ensure data entry of a 16-digit number
|
||||
|
||||
Let's see if `4137 8947 1175 5804` is a valid credit card number?
|
||||
|
||||
First, we enter it as a value and immediately break the number into its digits:
|
||||
First, we enter it as a value, the immediately break the number into its digits and call `reverse` to flip their order:
|
||||
|
||||
```{julia}
|
||||
x = 4137_8947_1175_5804 # _ in a number is ignored by parser
|
||||
xs = digits(x)
|
||||
xs = reverse(digits(x))
|
||||
```
|
||||
|
||||
We reverse the order, so the first number in digits is the largest place value in `xs`
|
||||
|
||||
Now, the 1st, 3rd, 5th, ... digit is doubled. We do this by creating a vector with the pattern 2,1,2, ... and then multiplying by this component by component:^[There are many different ways this alternate multiplying by `2` can be achieved, but most benefit from knowing an if-then-else command.]
|
||||
|
||||
```{julia}
|
||||
reverse!(xs)
|
||||
two_ones = repeat([2,1]; outer=length(xs) ÷ 2)
|
||||
xs = two_ones .* xs
|
||||
```
|
||||
|
||||
Now, the 1st, 3rd, 5th, ... digit is doubled. We do this through indexing:
|
||||
|
||||
```{julia}
|
||||
for i in 1:2:length(xs)
|
||||
xs[i] = 2 * xs[i]
|
||||
end
|
||||
```
|
||||
|
||||
Numbers greater than 9, have their digits added, then all the resulting numbers are added. This can be done with a generator:
|
||||
Numbers greater than 9, have their digits added, then all the resulting numbers are added. This can be done different ways. Here we use a generator:
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -747,5 +720,14 @@ Darn. A typo. is `4137 8047 1175 5804` a possible credit card number?
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
booleanq(true)
|
||||
let
|
||||
x = 4137_8047_1175_5804
|
||||
xs = digits(x)
|
||||
reverse!(xs)
|
||||
two_ones = repeat([2,1]; outer=length(xs) ÷ 2)
|
||||
xs = two_ones .* xs
|
||||
z = sum(sum(digits(xi)) for xi in xs)
|
||||
val = iszero(rem(z,10))
|
||||
booleanq(val)
|
||||
end
|
||||
```
|
||||
+21
-30
@@ -14,22 +14,18 @@ using CalculusWithJulia
|
||||
nothing
|
||||
```
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
imgfile = "figures/calculator.png"
|
||||
caption = "Screenshot of a calculator provided by the Google search engine."
|
||||
# ImageFile(:precalc, imgfile, caption)
|
||||
nothing
|
||||
```
|
||||
|
||||

|
||||
::: {#fig-google-calculator-screenshot}
|
||||

|
||||
|
||||
Screenshot of a calculator provided by the Google search engine
|
||||
:::
|
||||
|
||||
The Google calculator has a button `Ans` to refer to the answer to the previous evaluation. This is a form of memory. The last answer is stored in a specific place in memory for retrieval when `Ans` is used. In some calculators, more advanced memory features are possible. For some, it is possible to push values onto a stack of values for them to be referred to at a later time. This proves useful for complicated expressions, say, as the expression can be broken into smaller intermediate steps to be computed. These values can then be appropriately combined. This strategy is a good one, though the memory buttons can make its implementation a bit cumbersome.
|
||||
|
||||
|
||||
With `Julia`, as with other programming languages, it is very easy to refer to past evaluations. This is done by *assignment* whereby a computed value stored in memory is associated with a name (sometimes thought of as symbol or label). The name can be used to look up the value later. Assignment does not change the value of the object being assigned, it only introduces a reference to it.
|
||||
`Julia`, as with other programming languages, makes it is very easy to refer to many past evaluations.
|
||||
|
||||
This is done by *assignment* whereby the storage of a value in memory is associated with a name. This pairing results in a *variable* and the value can be referenced through the variable name. The variable name may be called an identifier and internally kept as a symbol. Julia is a dynamic language which means the pairing can be updated and the referenced values may have different storage types. An example of a built-in variable is the special variable `ans` which refers to the last computed value in an interactive session.
|
||||
|
||||
Assignment in `Julia` is handled by the equals sign and takes the general form `variable_name = value`. For example, here we assign values to the variables `x` and `y`
|
||||
|
||||
@@ -49,7 +45,7 @@ x
|
||||
Just typing a variable name (without a trailing semicolon) causes the assigned value to be displayed.
|
||||
|
||||
|
||||
Variable names can be reused (or reassigned), as here, where we redefine `x`:
|
||||
Variable names can be reused (or reassigned)^[The `Pluto` interface for `Julia` is idiosyncratic, as variables are *reactive*. This interface allows changes to a variable `x` to propagate to all other cells referring to `x`. Consequently, the variable name can only be assigned *once* per notebook **unless** the name is in some other namespace, which can be arranged by including the assignment inside a function or a `let` block.], as here, where we redefine `x`:
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -57,13 +53,8 @@ Variable names can be reused (or reassigned), as here, where we redefine `x`:
|
||||
x = 2
|
||||
```
|
||||
|
||||
:::{.callout-note}
|
||||
## Note
|
||||
The `Pluto` interface for `Julia` is idiosyncratic, as variables are *reactive*. This interface allows changes to a variable `x` to propagate to all other cells referring to `x`. Consequently, the variable name can only be assigned *once* per notebook **unless** the name is in some other namespace, which can be arranged by including the assignment inside a function or a `let` block.
|
||||
|
||||
:::
|
||||
|
||||
`Julia` is referred to as a "dynamic language" which means (in most cases) that a variable can be reassigned with a value of a different type, as we did with `x` where first it was assigned to a floating point value then to an integer value. (Though we meet some cases - generic functions - where `Julia` balks at reassigning a variable if the type is different.)
|
||||
`Julia` is referred to as a "dynamic language" which means (in most cases) that a variable can be reassigned with a value of a different type, as we did with `x` where first it was assigned to a floating point value then to an integer value. (Though we meet some cases---generic functions---where `Julia` balks at reassigning a variable if the type is different.)
|
||||
|
||||
|
||||
More importantly than displaying a value, is the use of variables to build up more complicated expressions. For example, to compute
|
||||
@@ -181,12 +172,18 @@ Q = R^(2/3) * S^(1/2) / n * A
|
||||
## Where math and computer notations diverge
|
||||
|
||||
|
||||
It is important to recognize that `=` to `Julia` is not in analogy to how $=$ is used in mathematical notation. The following `Julia` code is not an equation:
|
||||
It is important to recognize that `=` to `Julia` is not in analogy to how $=$ is used in mathematical notation.
|
||||
|
||||
First, set `x` to be $3$:
|
||||
|
||||
```{julia}
|
||||
x = 3
|
||||
```
|
||||
|
||||
The following `Julia` code is not an equation:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
x = 3
|
||||
x = x^2
|
||||
```
|
||||
|
||||
@@ -219,12 +216,12 @@ x = x - (x^2 - 2) / (2x)
|
||||
x = x - (x^2 - 2) / (2x)
|
||||
```
|
||||
|
||||
Repeating this last line will generate new values of `x` based on the previous one - no need for subscripts. This is exactly what the mathematical notation indicates is to be done.
|
||||
Repeating this last line will generate new values of `x` based on the previous one---no need for subscripts. This is exactly what the mathematical notation indicates is to be done.
|
||||
|
||||
::: {.callout-note}
|
||||
## Use of =
|
||||
|
||||
The distinction between ``=`` versus `=` is important and one area where common math notation and common computer notation diverge. The mathematical ``=`` indicates *equality*, and is often used with equations and also for assignment. Later, when symbolic math is introduced, the `~` symbol will be used to indicate an equation, though this is by convention and not part of base `Julia`. The computer syntax use of `=` is for *assignment* and *re-assignment*. Equality is tested with `==` and `===`.
|
||||
The distinction between ``=`` versus `=` is important and one area where common math notation and common computer notation diverge. The mathematical ``=`` indicates *equality*, and is often used with equations and also for assignment. Later, when symbolic math is introduced, the `~` symbol will be used to indicate an equation, though this is by convention and not part of base `Julia`. The computer syntax use of `=` is for *assignment* and *re-assignment*. Equality is tested with `==` and identicalness (or egal) with `===`.
|
||||
|
||||
:::
|
||||
|
||||
@@ -283,20 +280,14 @@ For example, we could have defined `theta` (`\theta[tab]`) and `v0` (`v\_0[tab]`
|
||||
θ = 45; v₀ = 200
|
||||
```
|
||||
|
||||
These notes often use Unicode alternatives for some variable.
|
||||
|
||||
:::{.callout-note}
|
||||
## Emojis
|
||||
There is even support for tab-completion of [emojis](https://github.com/JuliaLang/julia/blob/master/stdlib/REPL/src/emoji_symbols.jl) such as `\:snowman:[tab]` or `\:koala:[tab]`
|
||||
|
||||
:::
|
||||
|
||||
|
||||
:::{.callout-note}
|
||||
## Unicode
|
||||
These notes often use Unicode alternatives for some variable. Originally this was to avoid a requirement of `Pluto` of a single use of assigning to a variable name in a notebook without placing the assignment in a `let` block or a function body. Now, they are just for clarity through distinction.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
|
||||
|
||||
+527
-642
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user