Android Calculator
Making a precise calculator is not trivial. While \(10^{100} + 1 - 10^{100} = 1\), iOS’s calculator gives back \(0\). explores how Android’s calculator gets this right courtesy of .
Real number representations are fundamentally imprecise because we can’t squeeze in infinitely many real numbers into a finite number of bits. Floating-point representations have a base \(\beta\) (assumed to be even) and a precision \(p\), e.g., if \(\beta = 10\) and \(p = 3\), then \(0.1\) is represented as \(1.00 \times 10^{-1}\). IEEE 754, a dominant floating point representation, requires \(\beta = 2, p = 24\) for single precision and \(p = 53\) for double precision. In IEEE 754, \(0.1\) cannot be represented exactly as \(1/10\) is not a factor of \(2\); it is approximately \(1.10011001100110011001101 \times 2^{-4}\).
BigInteger
representations support as big a number as the host computer’s
memory allows.
pub struct BigNum {
/// true for positive, false for negative
sign: bool,
/// Base 1e9 digits, least significant first
digits: Vect<u32>,
}
With BigNum
, the calculator can handle all integers \(\mathbb{Z}\), e.g.,
\(10^{100} + 1 - 10^{100}\).
Rational numbers \(\mathbb{Q}\) are numbers that can be expressed as a ratio
of an integer to a non-zero integer. All integers are rational, but not all
rational numbers are integers, e.g., \(\frac{2}{9}\). Rational numbers can be handled by using BigNum
s for the numerator and
denominator.
Real algebraic numbers include ones that can’t be expressed as fractions, e.g., \(\sqrt{2}\). They can be represented as the polynomial equation they satisfy, e.g., \(\sqrt{2}\) can be expressed as \(x^2 - 2 = 0\) with a note that you want the positive root. Addition involves creating a new polynomial that has their sum as a root, and multiplication involves using polynomial composition and resultants.
To handle real numbers (including transcendental ones like \(\pi\) and \(e\) that are not the root of a non-zero polynomial with rational coefficients) , recursive real arithmetic (RRA) defines each number as a function that takes a requested tolerance and computes a rational guaranteed to be within tolerance of the real number it represents, e.g., for \(\pi\):
const pi = (tolerance) => {
let sum = 0;
let i = 0;
let term = 1;
// Leibniz formula: π/4 = 1 - 1/3 + 1/5 - 1/7 + ....
while (Math.abs(term) > tolerance) {
term = 1 / (2 * i + 1) * (i % 2 === 0 ? 1 : -1);
sum += term;
i++;
}
return sum * 4;
}
However, RRA can only tell you that \(1 - 1\) is within a rounding error of \(0.0000000000000\), but the user expects to see exactly \(0\). Checking whether two equal RRA numbers are indeed equal doesn’t terminate as you’ll infinitely keep computing them to finer precision. That said, not all constructive reals can be expressed with operations available on a calculator. Only these are:
- The 4 basic arithmetic operations, and square roots.
- \(sin, cos, tan\) trigonometric functions and their inverses.
- Exponential and (natural) logarithm functions
While ’s algorithm can detect that \(1 \ne 1 - e^{-e^{1000}}\), the algorithm requires more steps than there are atoms in the universe.
If the user enters \(6 \times 9\), only use rational arithmetic; however, when irrationals like \(\pi\) or \(\sqrt{2}\) come into play, use RRA. Even further, we can have symbolic representations to use in place of RRA functions, e.g.,
enum Real {
One,
Pi,
Exp(Rational),
Log(Rational),
Sin(Rational),
// ...
Other(RRA)
}
… so that in \((1 \times \pi) + (3 \times \pi)\) can use the symbolic representation of \(\pi\); the summation can be done by adding the rational parts. For a computation like \((1 \times \pi) + (3 \times \sqrt{2})\), we’d still need RRA. With this formulation, we have a calculator that is 100% correct, with mostly the correct UX and without the implementation complexity of a computer algebra system.
Why is \(\pi\) considered irrational? Isn’t \(22/7\) rational?
Well, \(22/7\) is a rational approximation to \(\pi\) that is commonly taught in schools. \(\pi = 3.14159265 \ldots \) while \( 22/7 = 3.14285714 \ldots \).