Compare commits
2 Commits
a67a3701be
...
e5d3fbee19
Author | SHA1 | Date | |
---|---|---|---|
e5d3fbee19 | |||
fb65744ee9 |
@ -3,7 +3,7 @@
|
||||
// Re-exports
|
||||
// All functions that maybe used by client code are listed here
|
||||
#import "misc.typ": *
|
||||
#import "object.typ": problem, definition, theorem
|
||||
#import "object.typ": problem, definition, theorem, example, remark, generic
|
||||
#import "solution.typ": if_solutions, if_no_solutions, solution
|
||||
|
||||
|
||||
|
@ -98,3 +98,11 @@
|
||||
#let theorem = _mkobj("Theorem")
|
||||
#let example = _mkobj("Example")
|
||||
#let remark = _mkobj("Remark")
|
||||
|
||||
#let generic(obj_content) = {
|
||||
block(
|
||||
above: 8mm,
|
||||
below: 2mm,
|
||||
text(weight: "bold", obj_content),
|
||||
)
|
||||
}
|
||||
|
@ -1,24 +0,0 @@
|
||||
% use [nosolutions] flag to hide solutions.
|
||||
% use [solutions] flag to show solutions.
|
||||
\documentclass[
|
||||
singlenumbering,
|
||||
solutions
|
||||
]{../../../lib/tex/ormc_handout}
|
||||
\usepackage{../../../lib/tex/macros}
|
||||
|
||||
\usepackage{listings}
|
||||
|
||||
\uptitlel{Advanced 2}
|
||||
\uptitler{\smallurl{}}
|
||||
\title{Fast Inverse Square Root}
|
||||
\subtitle{Prepared by Mark on \today}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\maketitle
|
||||
|
||||
\input{parts/1 int.tex}
|
||||
\input{parts/2 float.tex}
|
||||
\input{parts/3 approximate.tex}
|
||||
\input{parts/4 quake.tex}
|
||||
\end{document}
|
21
src/Advanced/Fast Inverse Root/main.typ
Normal file
21
src/Advanced/Fast Inverse Root/main.typ
Normal file
@ -0,0 +1,21 @@
|
||||
#import "@local/handout:0.1.0": *
|
||||
|
||||
#show: doc => handout(
|
||||
doc,
|
||||
group: "Advanced 2",
|
||||
|
||||
title: [Fast Inverse Root],
|
||||
by: "Mark",
|
||||
subtitle: "Based on a handout by Bryant Mathews",
|
||||
)
|
||||
|
||||
#include "parts/00 int.typ"
|
||||
#pagebreak()
|
||||
|
||||
#include "parts/01 float.typ"
|
||||
#pagebreak()
|
||||
|
||||
#include "parts/02 approx.typ"
|
||||
#pagebreak()
|
||||
|
||||
#include "parts/03 quake.typ"
|
7
src/Advanced/Fast Inverse Root/meta.toml
Normal file
7
src/Advanced/Fast Inverse Root/meta.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[metadata]
|
||||
title = "Fast Inverse Square Root"
|
||||
|
||||
|
||||
[publish]
|
||||
handout = true
|
||||
solutions = true
|
89
src/Advanced/Fast Inverse Root/parts/00 int.typ
Normal file
89
src/Advanced/Fast Inverse Root/parts/00 int.typ
Normal file
@ -0,0 +1,89 @@
|
||||
#import "@local/handout:0.1.0": *
|
||||
|
||||
= Integers
|
||||
|
||||
#definition()
|
||||
A _bit string_ is a string of binary digits. \
|
||||
In this handout, we'll denote bit strings with the prefix `0b`. \
|
||||
That is, $1010 =$ "one thousand and one," while $#text([`0b1001`]) = 2^3 + 2^0 = 9$
|
||||
|
||||
#v(2mm)
|
||||
We will seperate long bit strings with underscores for readability. \
|
||||
Underscores have no meaning: $#text([`0b1111_0000`]) = #text([`0b11110000`])$.
|
||||
|
||||
#problem()
|
||||
What is the value of the following bit strings, if we interpret them as integers in base 2?
|
||||
- `0b0001_1010`
|
||||
- `0b0110_0001`
|
||||
|
||||
#solution([
|
||||
- $#text([`0b0001_1010`]) = 2 + 8 + 16 = 26$
|
||||
- $#text([`0b0110_0001`]) = 1 + 32 + 64 = 95$
|
||||
])
|
||||
|
||||
#v(1fr)
|
||||
#pagebreak()
|
||||
|
||||
#definition()
|
||||
We can interpret a bit string in any number of ways. \
|
||||
One such interpretation is the _signed integer_, or `int` for short. \
|
||||
`ints` allow us to represent negative and positive integers using 32-bit strings.
|
||||
|
||||
#v(2mm)
|
||||
|
||||
The first bit of an `int` tells us its sign:
|
||||
- if the first bit is `1`, the _int_ represents a negative number;
|
||||
- if the first bit is `0`, it represents a positive number.
|
||||
|
||||
We do not need negative numbers today, so we will assume that the first bit is always zero. \
|
||||
#note([If you'd like to know how negative integers are written, look up "two's complement} after class.])
|
||||
|
||||
#v(2mm)
|
||||
|
||||
The value of a positive signed `long` is simply the value of its binary digits:
|
||||
- $#text([`0b00000000_00000000_00000000_00000000`]) = 0$
|
||||
- $#text([`0b00000000_00000000_00000000_00000011`]) = 3$
|
||||
- $#text([`0b00000000_00000000_00000000_00100000`]) = 32$
|
||||
- $#text([`0b00000000_00000000_00000000_10000010`]) = 130$
|
||||
|
||||
#problem()
|
||||
What is the largest number we can represent with a 32-bit `int`?
|
||||
|
||||
#solution([
|
||||
$#text([`0b01111111_11111111_11111111_11111111`]) = 2^(31)$
|
||||
])
|
||||
|
||||
#v(1fr)
|
||||
|
||||
#problem()
|
||||
What is the smallest possible number we can represented with a 32-bit `int`? \
|
||||
#hint([
|
||||
You do not need to know _how_ negative numbers are represented. \
|
||||
Assume that we do not skip any integers, and don't forget about zero.
|
||||
])
|
||||
|
||||
#solution([
|
||||
There are $2^(64)$ possible 32-bit patterns,
|
||||
of which 1 represents zero and $2^(31)$ represent positive numbers.
|
||||
We therefore have access to $2^(64) - 1 - 2^(31)$ negative numbers,
|
||||
giving us a minimum representable value of $-2^(31) + 1$.
|
||||
])
|
||||
|
||||
#v(1fr)
|
||||
|
||||
#problem()
|
||||
Find the value of each of the following 32-bit `int`s:
|
||||
- `0b00000000_00000000_00000101_00111001`
|
||||
- `0b00000000_00000000_00000001_00101100`
|
||||
- `0b00000000_00000000_00000100_10110000`
|
||||
#hint([The third conversion is easy---look carefully at the second.])
|
||||
|
||||
#solution([
|
||||
- $#text([`0b00000000_00000000_00000101_00111001`]) = 1337$
|
||||
- $#text([`0b00000000_00000000_00000001_00101100`]) = 300$
|
||||
- $#text([`0b00000000_00000000_00000010_01011000`]) = 1200$
|
||||
])
|
||||
Notice that the third int is the second shifted left twice (i.e, multiplied by 4)
|
||||
])
|
||||
|
||||
#v(2fr)
|
200
src/Advanced/Fast Inverse Root/parts/01 float.typ
Normal file
200
src/Advanced/Fast Inverse Root/parts/01 float.typ
Normal file
@ -0,0 +1,200 @@
|
||||
#import "@local/handout:0.1.0": *
|
||||
#import "@preview/cetz:0.3.1"
|
||||
|
||||
= Floats
|
||||
#definition()
|
||||
_Binary decimals_#footnote["decimal" is a misnomer, but that's ok.] are very similar to base-10 decimals. \
|
||||
In base 10, we interpret place value as follows:
|
||||
- $0.1 = 10^(-1)$
|
||||
- $0.03 = 3 times 10^(-2)$
|
||||
- $0.0208 = 2 times 10^(-2) + 8 times 10^(-4)$
|
||||
|
||||
#v(5mm)
|
||||
|
||||
We can do the same in base 2:
|
||||
- $#text([`0.1`]) = 2^(-1) = 0.5$
|
||||
- $#text([`0.011`]) = 2^(-2) + 2^(-3) = 0.375$
|
||||
- $#text([`101.01`]) = 5.125$
|
||||
|
||||
#v(5mm)
|
||||
|
||||
#problem()
|
||||
Rewrite the following binary decimals in base 10: \
|
||||
#note([You may leave your answer as a fraction.])
|
||||
- `1011.101`
|
||||
- `110.1101`
|
||||
|
||||
|
||||
#v(1fr)
|
||||
#pagebreak()
|
||||
|
||||
#definition()
|
||||
Another way we can interpret a bit string is as a _signed floating-point decimal_, or a `float` for short. \
|
||||
Floats represent a subset of the real numbers, and are interpreted as follows: \
|
||||
#note([The following only applies to floats that consist of 32 bits. We won't encounter any others today.])
|
||||
|
||||
#align(
|
||||
center,
|
||||
box(
|
||||
inset: 2mm,
|
||||
cetz.canvas({
|
||||
import cetz.draw: *
|
||||
|
||||
let chars = (
|
||||
`0`,
|
||||
`b`,
|
||||
`0`,
|
||||
`_`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`_`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`_`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`_`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
`0`,
|
||||
)
|
||||
|
||||
let x = 0
|
||||
for c in chars {
|
||||
content((x, 0), c)
|
||||
x += 0.25
|
||||
}
|
||||
|
||||
let y = -0.4
|
||||
line((0.3, y), (0.65, y))
|
||||
content((0.45, y - 0.2), [s])
|
||||
|
||||
line((0.85, y), (2.9, y))
|
||||
content((1.9, y - 0.2), [exponent])
|
||||
|
||||
line((3.10, y), (9.4, y))
|
||||
content((6.3, y - 0.2), [fraction])
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
- The first bit denotes the sign of the float's value
|
||||
We'll label it $s$. \
|
||||
If $s = #text([`1`])$, this float is negative; if $s = #text([`0`])$, it is positive.
|
||||
|
||||
- The next eight bits represent the _exponent_ of this float.
|
||||
#note([(we'll see what that means soon)]) \
|
||||
We'll call the value of this eight-bit binary integer $E$. \
|
||||
Naturally, $0 <= E <= 255$ #note([(since $E$ consist of eight bits.)])
|
||||
|
||||
- The remaining 23 bits represent the _fraction_ of this float, which we'll call $F$. \
|
||||
These 23 bits are interpreted as the fractional part of a binary decimal. \
|
||||
For example, the bits `0b10100000_00000000_00000000` represents $0.5 + 0.125 = 0.625$.
|
||||
|
||||
|
||||
#problem(label: "floata")
|
||||
Consider `0b01000001_10101000_00000000_00000000`. \
|
||||
Find the $s$, $E$, and $F$ we get if we interpret this bit string as a `float`. \
|
||||
#note([Leave $F$ as a sum of powers of two.])
|
||||
|
||||
#solution([
|
||||
$s = 0$ \
|
||||
$E = 258$ \
|
||||
$F = 2^31+2^19 = 2,621,440$
|
||||
])
|
||||
|
||||
#v(1fr)
|
||||
|
||||
|
||||
#definition()
|
||||
The final value of a float with sign $s$, exponent $E$, and fraction $F$ is
|
||||
|
||||
$
|
||||
(-1)^s times 2^(E - 127) times (1 + F / (2^(23)))
|
||||
$
|
||||
|
||||
Notice that this is very similar to decimal scientific notation, which is written as
|
||||
|
||||
$
|
||||
(-1)^s times 10^(e) times (f)
|
||||
$
|
||||
|
||||
#problem()
|
||||
Consider `0b01000001_10101000_00000000_00000000`. \
|
||||
This is the same bit string we used in @floata. \
|
||||
|
||||
#v(2mm)
|
||||
|
||||
What value do we get if we interpret this bit string as a float? \
|
||||
#hint([$21 div 16 = 1.3125$])
|
||||
|
||||
#solution([
|
||||
This is 21:
|
||||
$
|
||||
2^(131) times (1 + (2^(21) + 2^(19)) / (2^(23)))
|
||||
= 2^(4) times (1 + 0.25 + 0.0625)
|
||||
= 16 times (1.3125)
|
||||
= 21
|
||||
$
|
||||
])
|
||||
|
||||
#v(1fr)
|
||||
#pagebreak()
|
||||
|
||||
#problem()
|
||||
Encode $12.5$ as a float. \
|
||||
#hint([$12.5 div 8 = 1.5625$])
|
||||
|
||||
#solution([
|
||||
$
|
||||
12.5
|
||||
= 8 times 1.5625
|
||||
= 2^(3) times (1 + (0.5 + 0.0625))
|
||||
= 2^(130) times (1 + (2^(22) + 2^(19)) / (2^(23)))
|
||||
$
|
||||
|
||||
which is `0b01000001_01001000_00000000_00000000`. \
|
||||
])
|
||||
|
||||
|
||||
#v(1fr)
|
||||
|
||||
#definition()
|
||||
Say we have a bit string $x$. \
|
||||
We'll let $x_f$ denote the value we get if we interpret $x$ as a float, \
|
||||
and we'll let $x_i$ denote the value we get if we interpret $x$ an integer.
|
||||
|
||||
#problem()
|
||||
Let $x = #text[`0b01000001_01001000_00000000_00000000`]$. \
|
||||
What are $x_f$ and $x_i$? #note([As always, you may leave big numbers as powers of two.])
|
||||
#solution([
|
||||
$x_f = 12.5$
|
||||
|
||||
#v(2mm)
|
||||
|
||||
$x_i = 2^30 + 2^24 + 2^22 + 2^19 = 11,095,237,632$
|
||||
])
|
||||
|
||||
#v(1fr)
|
87
src/Advanced/Fast Inverse Root/parts/02 approx.typ
Normal file
87
src/Advanced/Fast Inverse Root/parts/02 approx.typ
Normal file
@ -0,0 +1,87 @@
|
||||
#import "@local/handout:0.1.0": *
|
||||
#import "@preview/cetz:0.3.1"
|
||||
#import "@preview/cetz-plot:0.1.0": plot, chart
|
||||
|
||||
= Integers and Floats
|
||||
|
||||
#generic("Observation:")
|
||||
For small values of $a$, $log_2(1 + a)$ is approximately equal to $a$. \
|
||||
Note that this equality is exact for $a = 0$ and $a = 1$, since $log_2(1) = 0$ and $log_2(2) = 1$.
|
||||
|
||||
#v(2mm)
|
||||
|
||||
We'll add a "correction term" $epsilon$ to this approximation, so that $log_2(1 + a) approx a + epsilon$.
|
||||
|
||||
#cetz.canvas({
|
||||
import cetz.draw: *
|
||||
|
||||
let f1(x) = calc.log(calc.abs(x + 1), base: 2)
|
||||
let f2(x) = x
|
||||
|
||||
// Set-up a thin axis style
|
||||
set-style(axes: (stroke: .5pt, tick: (stroke: .5pt)))
|
||||
|
||||
|
||||
plot.plot(
|
||||
size: (8, 8),
|
||||
x-tick-step: 0.2,
|
||||
y-tick-step: 0.2,
|
||||
y-min: 0,
|
||||
y-max: 1,
|
||||
x-min: 0,
|
||||
x-max: 1,
|
||||
legend: none,
|
||||
axis-style: "scientific-auto",
|
||||
|
||||
{
|
||||
let domain = (0, 10)
|
||||
|
||||
plot.add-fill-between(
|
||||
f1,
|
||||
f2,
|
||||
domain: domain,
|
||||
style: (stroke: none, fill: luma(75%)),
|
||||
)
|
||||
|
||||
plot.add(
|
||||
f1,
|
||||
domain: domain,
|
||||
label: $log(1+x)$,
|
||||
style: (stroke: black),
|
||||
)
|
||||
plot.add(f2, domain: domain, label: $x$, style: (stroke: black))
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
TODO: why? Graphs.
|
||||
|
||||
#problem(label: "convert")
|
||||
Use the fact that $log_2(1 + a) approx a + epsilon$ to approximate $log_2(x_f)$ in terms of $x_i$. \
|
||||
|
||||
#v(5mm)
|
||||
|
||||
Namely, show that
|
||||
$
|
||||
log_2(x_f) = (x_i) / (2^23) - 127 + epsilon
|
||||
$
|
||||
for some correction term term $epsilon$ \
|
||||
#note([
|
||||
In other words, we're finding an expression for $x$ as a float
|
||||
in terms of $x$ as an int.
|
||||
])
|
||||
|
||||
#solution([
|
||||
Let $E$ and $F$ be the exponent and float bits of $x_f$. \
|
||||
We then have:
|
||||
$
|
||||
log_2(x_f)
|
||||
&= log_2 ( 2^(E-127) times (1 + (F) / (2^23)) ) \
|
||||
&= E - 127 + log_2(1 + F / (2^23)) \
|
||||
& approx E-127 + F / (2^23) + epsilon \
|
||||
&= 1 / (2^23)(2^23 E + F) - 127 + epsilon \
|
||||
&= 1 / (2^23)(x_i) - 127 + epsilon
|
||||
$
|
||||
])
|
||||
|
||||
#v(1fr)
|
119
src/Advanced/Fast Inverse Root/parts/03 quake.typ
Normal file
119
src/Advanced/Fast Inverse Root/parts/03 quake.typ
Normal file
@ -0,0 +1,119 @@
|
||||
#import "@local/handout:0.1.0": *
|
||||
|
||||
= The Fast Inverse Square Root
|
||||
|
||||
|
||||
The following code is present in _Quake III Arena_ (1999):
|
||||
```c
|
||||
float Q_rsqrt( float number ) {
|
||||
long i = * ( long * ) &number;
|
||||
i = 0x5f3759df - ( i >> 1 );
|
||||
return * ( float * ) &i;
|
||||
}
|
||||
```
|
||||
|
||||
This code defines a function `Q_rsqrt` that consumes a float `number` and approximates its inverse square root.
|
||||
If we rewrite this using notation we're familiar with, we get the following:
|
||||
$
|
||||
#text[`Q_sqrt`] (n_f) = 6240089 - (n_i div 2)
|
||||
approx 1 / sqrt(n_f)
|
||||
$
|
||||
|
||||
#note([
|
||||
`0x5f3759df` is $6240089$ in hexadecimal. \
|
||||
It is a magic number hard-coded into `Q_sqrt`.
|
||||
])
|
||||
|
||||
#v(2mm)
|
||||
|
||||
Our goal in this section is to understand why this works:
|
||||
- How does Quake approximate $1 / sqrt(x)$ by simply subtracting and dividing by two?
|
||||
- What's special about $6240089$?
|
||||
|
||||
|
||||
#problem()
|
||||
Using basic log rules, rewrite $log_2(1 / sqrt(x))$ in terms of $log_2(x)$.
|
||||
|
||||
#solution([
|
||||
$
|
||||
log_2(1 / sqrt(x)) = (-1) / (2)log_2(x)
|
||||
$
|
||||
])
|
||||
|
||||
#v(1fr)
|
||||
#pagebreak()
|
||||
|
||||
#generic("Setup:")
|
||||
We are now ready to show that $#text[`Q_sqrt`] (x) approx 1/sqrt(x)$. \
|
||||
For convenience, let's call the bit string of the inverse square root $r$. \
|
||||
In other words,
|
||||
$
|
||||
r_f := 1 / (sqrt(n_f))
|
||||
$
|
||||
This is the value we want to approximate.
|
||||
|
||||
#problem(label: "finala")
|
||||
Find an approximation for $log_2(r_f)$ in terms of $n_i$ and $epsilon$ \
|
||||
#note[Remember, $epsilon$ is the correction constant in our approximation of $log_2(1 + a)$.]
|
||||
|
||||
#solution[
|
||||
$
|
||||
log_2(r_f)
|
||||
= log_2(1 / sqrt(n_f))
|
||||
= (-1) / 2 log_2(n_f)
|
||||
approx (-1) / 2 ( (n_i) / (2^23) + epsilon - 127 )
|
||||
$
|
||||
]
|
||||
|
||||
#v(1fr)
|
||||
|
||||
#problem(label: "finalb")
|
||||
Let's call the "magic number" in the code above $kappa$, so that
|
||||
$
|
||||
#text[`Q_sqrt`] (n_f) = kappa - (n_i div 2)
|
||||
$
|
||||
Use @convert and @finala to show that $#text[`Q_sqrt`] (n_f) approx r_i$
|
||||
|
||||
#solution[
|
||||
From @convert, we know that
|
||||
$
|
||||
log_2(r_f) approx (r_i) / (2^23) + epsilon - 127
|
||||
$
|
||||
|
||||
#note[
|
||||
Our approximation of $log_2(1+a)$ uses a fixed correction constant, \
|
||||
so the $epsilon$ here is equivalent to the $epsilon$ in @finala.
|
||||
]
|
||||
|
||||
Combining this with the result from \ref{finala}, we get:
|
||||
$
|
||||
(r_i) / (2^23) + epsilon - 127
|
||||
&approx (-1) / (2) ( (n_i) / (2^23) + epsilon - 127) \
|
||||
(r_i) / (2^23)
|
||||
&approx (-1) / (2) ( (n_i) / (2^23)) + 3 / 2 (epsilon - 127) \
|
||||
r_i
|
||||
&approx (-1) / 2 (n_i) + 2^23 3 / 2(epsilon - 127)
|
||||
= 2^23 3 / 2 (epsilon - 127) - (n_i) / 2
|
||||
$
|
||||
|
||||
#v(2mm)
|
||||
|
||||
This is exactly what we need! If we set $kappa$ to $(2^24)/3 (epsilon - 127)$, then
|
||||
$
|
||||
r_i approx kappa - (n_i div 2) = #text[`Q_sqrt`] (n_f)
|
||||
$
|
||||
]
|
||||
|
||||
#v(1fr)
|
||||
|
||||
#problem()
|
||||
What is the exact value of $kappa$ in terms of $epsilon$? \
|
||||
#hint[Look at @finalb. We already found it!]
|
||||
|
||||
#solution[
|
||||
This problem makes sure our students see that
|
||||
$kappa = (2^24)/3(epsilon - 127)$. \
|
||||
See the solution to @finalb.
|
||||
]
|
||||
|
||||
#if_no_solutions(v(2cm))
|
@ -1,101 +0,0 @@
|
||||
\section{Integers}
|
||||
|
||||
\definition{}
|
||||
A \textit{bit string} is a string of binary digits. \par
|
||||
In this handout, we'll denote bit strings with the prefix \texttt{0b}. \par
|
||||
That is, $1010 =$ \say{one thousand and one,} while $\texttt{0b1001} = 2^3 + 2^0 = 9$
|
||||
|
||||
\vspace{2mm}
|
||||
We will seperate long bit strings with underscores for readability. \par
|
||||
Underscores have no meaning: $\texttt{0b1111\_0000} = \texttt{0b11110000}$.
|
||||
|
||||
\problem{}
|
||||
What is the value of the following bit strings, if we interpret them as integers in base 2? \par
|
||||
\begin{itemize}
|
||||
\item \texttt{0b0001\_1010}
|
||||
\item \texttt{0b0110\_0001}
|
||||
\end{itemize}
|
||||
|
||||
\begin{solution}
|
||||
\begin{itemize}
|
||||
\item $\texttt{0b0001\_1010} = 2 + 8 + 16 = 26$
|
||||
\item $\texttt{0b0110\_0001} = 1 + 32 + 64 = 95$
|
||||
\end{itemize}
|
||||
\end{solution}
|
||||
|
||||
\vfill
|
||||
\pagebreak
|
||||
|
||||
|
||||
\definition{}
|
||||
We can interpret a bit string in any number of ways. \par
|
||||
One such interpretation is the \textit{signed integer}, or \texttt{int} for short. \par
|
||||
\texttt{ints} allow us to represent negative and positive integers using 32-bit strings.
|
||||
|
||||
\vspace{2mm}
|
||||
|
||||
The first bit of an \texttt{int} tells us its sign:
|
||||
\begin{itemize}
|
||||
\item if the first bit is \texttt{1}, the \textit{int} represents a negative number;
|
||||
\item if the first bit is \texttt{0}, it represents a positive number.
|
||||
\end{itemize}
|
||||
We do not need negative numbers today, so we will assume that the first bit is always zero. \par
|
||||
\note{If you'd like to know how negative integers are written, look up \say{two's complement} after class.}
|
||||
|
||||
\vspace{2mm}
|
||||
|
||||
The value of a positive signed \texttt{long} is simply the value of its binary digits:
|
||||
\begin{itemize}
|
||||
\item $\texttt{0b00000000\_00000000\_00000000\_00000000} = 0$
|
||||
\item $\texttt{0b00000000\_00000000\_00000000\_00000011} = 3$
|
||||
\item $\texttt{0b00000000\_00000000\_00000000\_00100000} = 32$
|
||||
\item $\texttt{0b00000000\_00000000\_00000000\_10000010} = 130$
|
||||
\end{itemize}
|
||||
|
||||
\problem{}
|
||||
What is the largest number we can represent with a 32-bit \texttt{int}?
|
||||
|
||||
\begin{solution}
|
||||
$\texttt{0b01111111\_11111111\_11111111\_11111111} = 2^{31}$
|
||||
\end{solution}
|
||||
|
||||
\vfill
|
||||
|
||||
\problem{}
|
||||
What is the smallest possible number we can represented with a 32-bit \texttt{int}? \par
|
||||
\hint{
|
||||
You do not need to know \textit{how} negative numbers are represented. \par
|
||||
Assume that we do not skip any integers, and don't forget about zero.
|
||||
}
|
||||
|
||||
\begin{solution}
|
||||
There are $2^{64}$ possible 32-bit patterns,
|
||||
of which 1 represents zero and $2^{31}$ represent positive numbers.
|
||||
We therefore have access to $2^{64} - 1 - 2^{31}$ negative numbers,
|
||||
giving us a minimum representable value of $-2^{31} + 1$.
|
||||
\end{solution}
|
||||
|
||||
\vfill
|
||||
|
||||
\problem{}
|
||||
Find the value of each of the following 32-bit \texttt{int}s:
|
||||
\begin{itemize}
|
||||
\item \texttt{0b00000000\_00000000\_00000101\_00111001}
|
||||
\item \texttt{0b00000000\_00000000\_00000001\_00101100}
|
||||
\item \texttt{0b00000000\_00000000\_00000100\_10110000}
|
||||
\end{itemize}
|
||||
\hint{The third conversion is easy---look carefully at the second.}
|
||||
|
||||
\begin{solution}
|
||||
\begin{itemize}
|
||||
\item $\texttt{0b00000000\_00000000\_00000101\_00111001} = 1337$
|
||||
\item $\texttt{0b00000000\_00000000\_00000001\_00101100} = 300$
|
||||
\item $\texttt{0b00000000\_00000000\_00000010\_01011000} = 1200$
|
||||
\end{itemize}
|
||||
|
||||
Notice that the third long is the second shifted left twice (i.e, multiplied by 4)
|
||||
\end{solution}
|
||||
|
||||
\vfill
|
||||
\vfill
|
||||
\pagebreak
|
@ -1,186 +0,0 @@
|
||||
\section{Floats}
|
||||
|
||||
\definition{}
|
||||
\textit{Binary decimals}\footnotemark{} are very similar to base-10 decimals. \par
|
||||
In base 10, we interpret place value as follows:
|
||||
\begin{itemize}
|
||||
\item $0.1 = 10^{-1}$
|
||||
\item $0.03 = 3 \ \times 10^{-2}$
|
||||
\item $0.0208 = 2 \times 10^{-2} + 8 \times 10^{-4}$
|
||||
\end{itemize}
|
||||
|
||||
\footnotetext{\say{decimal} is a misnomer, but that's ok.}
|
||||
|
||||
\vspace{5mm}
|
||||
|
||||
We can do the same in base 2:
|
||||
\begin{itemize}
|
||||
\item $\texttt{0.1} = 2^{-1} = 0.5$
|
||||
\item $\texttt{0.011} = 2^{-2} + 2^{-3} = 0.375$
|
||||
\item $\texttt{101.01} = 5.125$
|
||||
\end{itemize}
|
||||
|
||||
\vspace{5mm}
|
||||
|
||||
\problem{}
|
||||
Rewrite the following binary decimals in base 10: \par
|
||||
\note{You may leave your answer as a fraction.}
|
||||
\begin{itemize}
|
||||
\item $\texttt{1011.101}$
|
||||
\item $\texttt{110.1101}$
|
||||
\end{itemize}
|
||||
|
||||
\vfill
|
||||
\pagebreak
|
||||
|
||||
\definition{}
|
||||
Another way we can interpret a bit string is as a \textit{signed floating-point decimal}, or a \texttt{float} for short. \par
|
||||
Floats represent a subset of the real numbers, and are interpreted as follows: \par
|
||||
\note{The following only applies to floats that consist of 32 bits. We won't encounter any others today.}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}
|
||||
\node[anchor=south west] at (0, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (0.25, 0) {\texttt{\texttt{b}}};
|
||||
\node[anchor=south west] at (0.50, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (0.75, 0) {\texttt{\texttt{\_}}};
|
||||
|
||||
\node[anchor=south west] at (1.00, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (1.25, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (1.50, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (1.75, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (2.00, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (2.25, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (2.50, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (2.75, 0) {\texttt{\texttt{0}}};
|
||||
|
||||
\node[anchor=south west] at (3.00, 0) {\texttt{\texttt{\_}}};
|
||||
\node[anchor=south west] at (3.25, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (3.50, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (3.75, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (4.00, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (4.25, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (4.50, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (4.75, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (5.00, 0) {\texttt{\texttt{\_}}};
|
||||
|
||||
\node[anchor=south west] at (5.25, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (5.50, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (5.75, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (6.00, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (6.25, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (6.50, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (6.75, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (7.00, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (7.25, 0) {\texttt{\texttt{\_}}};
|
||||
|
||||
\node[anchor=south west] at (7.50, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (7.75, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (8.00, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (8.25, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (8.50, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (8.75, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (9.00, 0) {\texttt{\texttt{0}}};
|
||||
\node[anchor=south west] at (9.25, 0) {\texttt{\texttt{0}}};
|
||||
|
||||
|
||||
\draw (0.50, 0) -- (0.95, 0) node [midway, below=1mm] {sign};
|
||||
\draw (1.05, 0) -- (3.15, 0) node [midway, below=1mm] {exponent};
|
||||
\draw (3.30, 0) -- (9.70, 0) node [midway, below=1mm] {fraction};
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
|
||||
\begin{itemize}[itemsep = 2mm]
|
||||
\item The first bit denotes the sign of the float's value. We'll label it $s$. \par
|
||||
If $s = \texttt{1}$, this float is negative; if $s = \texttt{0}$, it is positive.
|
||||
|
||||
\item The next eight bits represent the \textit{exponent} of this float. \note{(we'll see what that means soon)}\par
|
||||
We'll call the value of this eight-bit binary integer $E$. \par
|
||||
Naturally, $0 \leq E \leq 255$ \note{(since $E$ consist of eight bits.)}
|
||||
|
||||
\item The remaining 23 bits represent the \textit{fraction} of this float, which we'll call $F$. \par
|
||||
These 23 bits are interpreted as the fractional part of a binary decimal. \par
|
||||
For example, the bits \texttt{0b1010000\_00000000\_00000000} represents $0.5 + 0.125 = 0.625$.
|
||||
\end{itemize}
|
||||
|
||||
\problem{}<floata>
|
||||
Consider \texttt{0b01000001\_10101000\_00000000\_00000000}. \par
|
||||
Find the $s$, $E$, and $F$ we get if we interpret this bit string as a \texttt{float}. \par
|
||||
\note[Note]{Leave $F$ as a sum of powers of two.}
|
||||
|
||||
\begin{solution}
|
||||
$s = 0$ \par
|
||||
$E = 258$ \par
|
||||
$F = 2^{31}+2^{19} = 2,621,440$
|
||||
\end{solution}
|
||||
|
||||
\vfill
|
||||
|
||||
|
||||
\definition{}
|
||||
The final value of a float with sign $s$, exponent $E$, and fraction $F$ is
|
||||
\begin{equation*}
|
||||
(-1)^s ~\times~ 2^{E - 127} ~\times~ \left(1 + \frac{F}{2^{23}}\right)
|
||||
\end{equation*}
|
||||
|
||||
Notice that this is very similar to decimal scientific notation, which is written as
|
||||
\begin{equation*}
|
||||
(-1)^s ~\times~ 10^{e} ~\times~ (f)
|
||||
\end{equation*}
|
||||
|
||||
\problem{}
|
||||
Consider \texttt{0b01000001\_10101000\_00000000\_00000000}. \par
|
||||
This is the same bit string we used in \ref{floata}. \par
|
||||
|
||||
\vspace{2mm}
|
||||
|
||||
What value do we get if we interpret this bit string as a float? \par
|
||||
\hint{$21 \div 16 = 1.3125$}
|
||||
|
||||
\begin{solution}
|
||||
This is 21:
|
||||
\begin{equation*}
|
||||
2^{131} \times \biggl(1 + \frac{2^{21} + 2^{19}}{2^{23}}\biggr)
|
||||
~=~ 2^{4} \times (1 + 0.25 + 0.0625)
|
||||
~=~ 16 \times (1.3125)
|
||||
~=~ 21
|
||||
\end{equation*}
|
||||
\end{solution}
|
||||
|
||||
\vfill
|
||||
\pagebreak
|
||||
|
||||
\problem{}
|
||||
Encode $12.5$ as a float. \par
|
||||
\hint{$12.5 \div 8 = 1.5625$}
|
||||
|
||||
\begin{solution}
|
||||
\begin{equation*}
|
||||
12.5
|
||||
~=~ 8 \times 1.5625
|
||||
~=~ 2^{3} \times \biggl(1 + (0.5 + 0.0625)\biggr)
|
||||
~=~ 2^{130} \times \biggl(1 + \frac{2^{22} + 2^{19}}{2^{23}}\biggr)
|
||||
\end{equation*}
|
||||
|
||||
which is \texttt{0b01000001\_01001000\_00000000\_00000000}. \par
|
||||
\end{solution}
|
||||
|
||||
|
||||
\vfill
|
||||
|
||||
\definition{}
|
||||
Say we have a bit string $x$. \par
|
||||
We'll let $x_f$ denote the value we get if we interpret $x$ as a float, \par
|
||||
and we'll let $x_i$ denote the value we get if we interpret $x$ an integer.
|
||||
|
||||
\problem{}
|
||||
Let $x = \texttt{0b01000001\_01001000\_00000000\_00000000}$. \par
|
||||
What are $x_f$ and $x_i$? \note{As always, you may leave big numbers as powers of two.}
|
||||
\begin{solution}
|
||||
$x_f = 12.5$ \par
|
||||
\vspace{2mm}
|
||||
$x_i = 2^{30} + 2^{24} + 2^{22} + 2^{19} = 11,095,237,632$
|
||||
\end{solution}
|
||||
|
||||
\vfill
|
||||
|
||||
\pagebreak
|
@ -1,42 +0,0 @@
|
||||
\section{Integers and Floats}
|
||||
|
||||
\generic{Observation:}
|
||||
For small values of $a$, $\log_2(1 + a)$ is approximately equal to $a$. \par
|
||||
Note that this equality is exact for $a = 0$ and $a = 1$, since $\log_2(1) = 0$ and $\log_2(2) = 1$.
|
||||
|
||||
\vspace{2mm}
|
||||
|
||||
We'll add a \say{correction term} $\varepsilon$ to this approximation, so that $\log_2(1 + a) \approx a + \varepsilon$.
|
||||
|
||||
TODO: why? Graphs.
|
||||
|
||||
\problem{}<convert>
|
||||
Use the fact that $\log_2(1 + a) \approx a + \varepsilon$ to approximate $\log_2(x_f)$ in terms of $x_i$. \par
|
||||
|
||||
\vspace{5mm}
|
||||
|
||||
Namely, show that
|
||||
\begin{equation*}
|
||||
\log_2(x_f) ~=~ \frac{x_i}{2^{23}} - 127 + \varepsilon
|
||||
\end{equation*}
|
||||
for some correction term term $\varepsilon$ \par
|
||||
\note{
|
||||
In other words, we're finding an expression for $x$ as a float
|
||||
in terms of $x$ as an int.
|
||||
}
|
||||
|
||||
\begin{solution}
|
||||
Let $E$ and $F$ be the exponent and float bits of $x_f$. \par
|
||||
We then have:
|
||||
\begin{align*}
|
||||
\log_2(x_f)
|
||||
&=~ \log_2 \left( 2^{E-127} \times \left(1 + \frac{F}{2^{23}}\right) \right) \\
|
||||
&=~ E-127 + \log_2\left(1 + \frac{F}{2^{23}}\right) \\
|
||||
&\approx~ E-127 + \frac{F}{2^{23}} + \varepsilon \\
|
||||
&=~ \frac{1}{2^{23}}(2^{23}E + F) - 127 + \varepsilon \\
|
||||
&=~ \frac{1}{2^{23}}(x_i) - 127 + \varepsilon
|
||||
\end{align*}
|
||||
\end{solution}
|
||||
|
||||
\vfill
|
||||
\pagebreak
|
@ -1,132 +0,0 @@
|
||||
\section{The Fast Inverse Square Root}
|
||||
|
||||
The following code is present in \textit{Quake III Arena} (1999):
|
||||
\lstset{
|
||||
breaklines=false,
|
||||
numbersep=5pt,
|
||||
xrightmargin=0in
|
||||
}
|
||||
\begin{lstlisting}[language=C]
|
||||
float Q_rsqrt( float number ) {
|
||||
long i = * ( long * ) &number;
|
||||
i = 0x5f3759df - ( i >> 1 );
|
||||
return * ( float * ) &i;
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
This code defines a function \texttt{Q\_rsqrt} that consumes a float named
|
||||
\texttt{number} and approximates its inverse square root (in other words, \texttt{Q\_rsqrt} computes $1/\sqrt{\texttt{number}}$).
|
||||
|
||||
\vspace{2mm}
|
||||
|
||||
If we rewrite this using notation we're familiar with, we get the following:
|
||||
\begin{equation*}
|
||||
\texttt{Q\_sqrt}(n_f) = 6240089 - (n_i \div 2) \approx \frac{1}{\sqrt{n_f}}
|
||||
\end{equation*}
|
||||
|
||||
\note{
|
||||
\texttt{0x5f3759df} is $6240089$ in hexadecimal. \par
|
||||
It is a magic number hard-coded into \texttt{Q\_sqrt}.
|
||||
}
|
||||
|
||||
\vspace{2mm}
|
||||
|
||||
Our goal in this section is to understand why this works: \par
|
||||
\begin{itemize}
|
||||
\item How does Quake approximate $\frac{1}{\sqrt{x}}$ by simply subtracting and dividing by two?
|
||||
\item What's special about $6240089$?
|
||||
\end{itemize}
|
||||
|
||||
|
||||
\problem{}
|
||||
Using basic log rules, rewrite $\log_2(1 / \sqrt{x})$ in terms of $\log_2(x)$.
|
||||
|
||||
\begin{solution}
|
||||
\begin{equation*}
|
||||
\log_2(1 / \sqrt{x}) = \frac{-1}{2}\log_2(x)
|
||||
\end{equation*}
|
||||
\end{solution}
|
||||
|
||||
|
||||
\vfill
|
||||
\pagebreak
|
||||
|
||||
\generic{Setup:}
|
||||
We are now ready to show that $\texttt{Q\_sqrt} \approx \frac{1}{\sqrt{n_f}}$. \par
|
||||
For convenience, let's call the bit string of the inverse square root $r$. \par
|
||||
In other words,
|
||||
\begin{equation*}
|
||||
r_f := \frac{1}{\sqrt{n_f}}
|
||||
\end{equation*}
|
||||
This is the value we want to approximate.
|
||||
|
||||
\problem{}<finala>
|
||||
Find an approximation for $\log_2(r_f)$ in terms of $n_i$ and $\varepsilon$ \par
|
||||
\note{Remember, $\varepsilon$ is the correction constant in our approximation of $\log_2(1 + a)$.}
|
||||
|
||||
\begin{solution}
|
||||
\begin{equation*}
|
||||
\log_2(r_f)
|
||||
~=~ \log_2(\frac{1}{\sqrt{n_f}})
|
||||
~=~ \frac{-1}{2}\log_2(n_f)
|
||||
~\approx~ \frac{-1}{2}\left( \frac{n_i}{2^{23}} + \varepsilon - 127 \right)
|
||||
\end{equation*}
|
||||
\end{solution}
|
||||
|
||||
\vfill
|
||||
|
||||
\problem{}<finalb>
|
||||
Let's call the \say{magic number} in the code above $\kappa$, so that
|
||||
\begin{equation*}
|
||||
\texttt{Q\_sqrt}(n_f) = \kappa - (n_i \div 2)
|
||||
\end{equation*}
|
||||
Use problems \ref{num:convert} and \ref{num:finala} to show that $\texttt{Q\_sqrt}(n_f) \approx r_i$. \par
|
||||
|
||||
\begin{solution}
|
||||
From \ref{convert}, we know that
|
||||
\begin{equation*}
|
||||
\log_2(r_f) \approx \frac{r_i}{2^{23}} + \varepsilon - 127
|
||||
\end{equation*}
|
||||
|
||||
\note{
|
||||
Our approximation of $\log_2(1+a)$ uses a fixed correction constant, \par
|
||||
so the $\varepsilon$ here is equivalent to the $\varepsilon$ in \ref{finala}.
|
||||
}
|
||||
|
||||
Combining this with the result from \ref{finala}, we get:
|
||||
\begin{align*}
|
||||
\frac{r_i}{2^{23}} + \varepsilon - 127
|
||||
&~\approx~\frac{-1}{2}\left( \frac{n_i}{2^{23}} + \varepsilon - 127 \right) \\
|
||||
\frac{r_i}{2^{23}}
|
||||
&~\approx~\frac{-1}{2}\left( \frac{n_i}{2^{23}} \right) + \frac{3}{2}(\varepsilon - 127) \\
|
||||
r_i
|
||||
&~\approx~\frac{-1}{2}\left(n_i \right) + 2^{23}\frac{3}{2}(\varepsilon - 127)
|
||||
~=~ 2^{23}\frac{3}{2}(\varepsilon - 127) - \frac{n_i}{2}
|
||||
\end{align*}
|
||||
|
||||
\vspace{2mm}
|
||||
|
||||
This is exactly what we need! If we set $\kappa$ to $\frac{2^{24}}{3}(\varepsilon - 127)$, then
|
||||
\begin{equation*}
|
||||
r_i ~\approx~ \kappa - (n_i \div 2) ~=~ \texttt{Q\_sqrt}(n_f)
|
||||
\end{equation*}
|
||||
\end{solution}
|
||||
|
||||
\vfill
|
||||
|
||||
\problem{}
|
||||
What is the exact value of $\kappa$ in terms of $\varepsilon$? \par
|
||||
\hint{Look at \ref{finalb}. We already found it!}
|
||||
|
||||
\begin{solution}
|
||||
This problem makes sure our students see that
|
||||
$\kappa = \frac{2^{24}}{3}(\varepsilon - 127)$. \par
|
||||
See the solution to \ref{finalb}.
|
||||
\end{solution}
|
||||
|
||||
\makeatletter
|
||||
\if@solutions\else
|
||||
\vspace{2cm}
|
||||
\fi\makeatother
|
||||
|
||||
\pagebreak
|
Loading…
x
Reference in New Issue
Block a user