\documentclass[11pt]{article}
\input{template}
\begin{document}
\title{Arbitrary Sized Integers}
\author{Sam Moore, David Gow}
\maketitle
\section*{Abstract}
We have implemented arbitrary sized integers which sometimes don't segfault, using a combination of the C++ standard library and stand alone x86-64 assembly. Performance tests reveal that we would have been better off just using the GNU Multiprecision Library (GMP).
\section{Integer Representation}
A positive integer (natural number) can be written as the sum of smaller integers ``digits'' multiplied by powers of a base.
\begin{align}
z &= \displaystyle\sum_{i=0}^{\infty} d_i \beta^{i}
\end{align}
Where each digit $d_i < \beta$ the base. A set of $\beta$ unique symbols are used to represent values of $d_i$.
A fixed size representation truncates the sum at some $i=N$, which can represent all values $0 \leq z \leq \beta^{n+1}-1$.
A seperate sign symbol (eg: '-') can be used to represent negative integers using the same digit sum.
Example in base 10 (decimal):
\begin{align}
5682_{10} &= 5\times10^3 + 6\times10^2 + 8\times10^1 + 2\times10^0
\end{align}
In base 2 (binary) the same integer is:
\begin{align}
1011000110010_2 &= 1\times2^{12} + 0\times2^{11} + \text{ ...} + 0\times2^0
\end{align}
\subsection{Representation on computer hardware}
Computer hardware implements operations for fixed size integers. The base is $\beta = 2$ and the digits are $\{0, 1\}$. The most significant bit can be reserved for the sign instead of a digit.
We can construct larger size integers by considering some sequence of fixed size integers to be individual digits. In practice we will still be limited by the memory and processing time required for ``big'' integers.
For example, we can represent $5682_{10}$ as a single 16 bit digit or as the sum of two 8 bit digits.
Each digit is being written in base 2 or 10 because there is not a universal base with $\geq2^8$ unique symbols.
\begin{align}
5682_{10} &= 0001011000110010_2 = 10110_2\times2^8+ 110010_2\times 2^0 \\
&= 22_{10}\times2^{8} + 50_{10}\times2^{0}
\end{align}
\section{Addition Algorithms}
Addition $s = a + b$ is done by adding digits from least to most significant.
\begin{align*}
s = \displaystyle\sum_{i=0}^{\infty} (a_i + b_i) \beta^{i}
\end{align*}
Considering the contributions to the sum of the $i^\text{th}$ and $(i+1)^\text{th}$ digits:
\begin{align}
s_i\beta^i + s_{i+1}\beta^{i+1} &= (a_i+b_i)\beta^i + (a_i+b_i)\beta^{i+1} \\
\implies s_i + s_{i+1}\beta &= (a_i+b_i) + (a_{i+1}+b_{i+1})\beta \\
\end{align}
If the sum $a_i + b_i \geq \beta$, ie: It cannot be represented in base $\beta$, then we can rewrite this as:
\begin{align}
s_i + s_{i+1}\beta &= \beta + (a_i+b_i-\beta) + (a_{i+1}+b_{i+1})\beta \\
&= (a_i+b_i-\beta) + (a_{i+1}+b_{i+1}+1)\beta
\end{align}
So we can use the digits $s_i = (a_i+b_i-\beta) < \beta$ and $s_{i+1} = (a_{i+1}+b_{i+1}+1)$.
This operation is the \emph{carry}\footnote{I'm pretty sure that is not a rigorous definition but close enough}.
The x64 instruction set includes an \emph{add with carry} instruction \verb/adc/ which will add fixed sized digits and set a flag to indicate a carry. This allows for easy adding of an array of digits representing an arbitrary sized integer.
\section{Subtraction Algorithms}
Similarly, subtraction $s = a - b$ is done from least to most significant digit. If the result of $a_i - b_i < 0$ then we \emph{borrow} from a higher digit.
\begin{align*}
s_i + s_{i+1}\beta &= \beta + (a_i-b_i+\beta) + (a_{i+1}-b_{i+1}-1)\beta
\end{align*}
The x64 instruction set also includes a \emph{subtract with borrow} instruction \verb/sbb/ which will set a borrow flag.
\section{Multiplication Algorithms}
In general, the result of multiplying two $n$ digit numbers may require up to $2n$ digits.
\section{Division Algorithms}
\subsection{Naive Algorithm}
\subsection{Shifting Algorithm}
\section{Base conversion}
Since humans are not very good at understanding binary, it is convenient to convert integer representations from one base to another.
\section{Performance Comparison of IPDF::Arbint and GMP Integers}
We repeated 1000 trials of the four basic operations on arbitrary integers initialised from \verb/rand(3)/
Here are the average IR costs per operation collected using the \emph{callgrind} tool with the memory analysis program \emph{valgrind}.
\begin{figure}[H]
\centering\begin{tabular}{|c|c|c|c|}
\hline
{\bf Operation} & {\bf IR Cost Arbint} & {\bf IR Cost Gmpint} & {\bf Arbint/Gmpint}\\ \hline
*= & 3957 & 255 & 15.6 \\ \hline
/= & 395008 & 388 & 1018.1\\ \hline
+= & 252 & 98 & 2.5 \\ \hline
-= & 458 & 102 & 4.5\\
\hline
\end{tabular}
\caption{GMP wins}
\end{figure}
Clearly we are not as good at implementing arbitrary integer arithmetic as the GMP project. We are particularly bad at division. This is probably because we used the second algorithm on wikipedia.
Examining the GMP source code shows that the library is mostly implemented using highly optimised assembly which is selected based on the build target. We've used C++ classes with all their overhead. We also used a shittier division algorithm although our addition and subtraction are pretty similar.
\section{Conclusion}
Just use GMP.
\end{document}