Rationalising Denominators 1: Fractional Powers
Posts in this series:
Introduction
I’ve spent the last few weeks playing around with radicals, looking for a simple representation that will fit neatly into my Ivory Tower library. After a few false starts, I’ve cobbled together a neat little Python library, which I thought was worth sharing across a few blog posts.
Powers
We’ll start by defining a Power
as a pair of numbers, which we’ll
call a base
and an exp
onent:
The base
can be any natural number,
i.e. a whole number which is either positive or zero.
The exp
onent can be a Fraction
(a numerator
over a denominator
), but must obey certain
rules:
- We do not allow negative
exp
onents - If the
base
is 0, theexp
onent cannot also be 0.
Here’s a simple implementation in Python:
from fractions import Fraction
def Power(base: int, exp: Fraction) -> Tuple[int, Fraction]:
assert base >= 0, f"Power {base}^{exp} cannot have negative base"
assert exp >= 0, f"Power {base}^{exp} cannot have negative exponent"
if base == 0:
assert exp != 0, f"Power 0^0 is undefined"
return (base, exp)
I’ve given this function an uppercased name, to indicate that we’ll
use Power
as a type annotation as
well as for constructing values. Here are some constants for this Power
type, as well as for exp
onents:
= Fraction(0, 1)
zero = Fraction(1, 1)
one = Fraction(1, 2)
half
= Power(0, one)
power_zero = Power(1, one) power_one
Normalisation
Thankfully, Python’s Fraction
will automatically reduce values to their “normal form”, e.g. calling
Fraction(2, 4)
will return the value Fraction(1, 2)
.
However, there are other redundancies in our Power
type that will not simplify
automatically; especially values involving the numbers 0 and 1. For
example the following values all represent the number 1:
Power(1, Fraction(1, 1))
Power(1, Fraction(2, 1))
Power(2, Fraction(0, 1))
Powers of zero
When the base
is 0, we don’t
allow the exp
onent to be 0 (since
that’s not well-defined mathematically). For every other exp
onent, there is redundancy, since 0
raised to any non-zero power is 0. We can avoid this redundancy by
choosing a particular exp
onent to
be “normal”, and replace all other exp
onents of 0 with the normal exp
onent. I’ll pick the number 1 to be
our normal exp
onent and add the
following lines to our Power
function to perform this normalisation:
if base == 0:
= one exp
Powers of one
When the base
is 1, we can add
1 to the exp
onent without
changing the overall value; since that corresponds to multiplying the
result by the base
, which in this
case means multiplying by 1, which is redundant. We can reduce these
exp
onents to avoid this
redundancy, by repeatedly taking away 1 until it becomes less than 1.
This corresponds to the modulo operation, with
modulus
of 1:
if base == 1:
= exp % 1 exp
Zeroth powers
Our final normalisation applies when the exp
onent is 0: any non-zero number
raised to the power of 0 gives a result of 1. Hence we can choose a
“normal” value for the base
, say
the number 1, and replace any other base
with that:
if exp == 0:
= 1 base
This complements the previous rule, since the modulo operation could
result in the value Power(1, Fraction(0, 1))
.
Conclusion
Here’s our overall implementation of Power
:
from fractions import Fraction
def Power(base: int, exp: Fraction) -> Tuple[Base, Exponent]:
assert base >= 0, f"Power {base}^{exp} cannot have negative base"
assert exp >= 0, f"Power {base}^{exp} cannot have negative exponent"
if base == 0:
assert exp != 0, f"Power 0^0 is undefined"
# Normalise all other powers of 0 to 0^1, since they're equivalent
= one
exp if base == 1:
# Remove whole powers of 1, since they just multiply by one
= exp % 1
exp if exp == 0:
# Anything to the power of zero is one. Normalise to 1^1.
= (1, one)
base, exp return (base, exp)
= Fraction(0, 1)
zero = Fraction(1, 1)
one = Fraction(1, 2)
half
= Power(0, Fraction(1, 1))
power_zero = Power(1, Fraction(1, 1)) power_one
So far this is a pretty simple way to represent numbers, but it turns out to be quite powerful. We’ve implemented some normalisation steps, but there are still some redundancies; e.g. the number 4 can be represented in many ways, like:
Power(4, one)
Power(2, Fraction(2, 1))
Power(16, half)
- etc.
In the next post we’ll extend this to products of powers.