We provide two new functions, `numerator(m)` and `denominator(m)`, for a
Magnitude `m`. They fulfill the following conditions:
1. `numerator(m)` and `denominator(m)` are always integer Magnitudes.
2. If `m` is rational, then `m == numerator(m) / denominator(m)`.
If `m` is _not_ rational, then the numerator and denominator are not
especially meaningful (there is no uniquely defined "leftover irrational
part"). However, we choose a convention that matches how humans would
write a mixed number. For example, sqrt(27/16) would have a numerator
of 3, denominator of 4, and a "leftover part" of sqrt(3), matching the
"human" way of writing this as [(3 * sqrt(3)) / 4]. This has no use
yet, but it may later be useful in printing the Magnitude of an
anonymous Unit for end users.
To further reduce friction for the upcoming migration, we provide an
implicit conversion from a Magnitude to a `ratio`. We restrict this
operation to rational Magnitudes, and guard this with a `static_assert`.
This stems from an earlier mistake where I was using primes in the first
wheel, rather than coprimes-to-the-basis. 1 is not prime, so we used to
need to handle it separately (in an implementation which was, to be
clear, wrong). It _is_ coprime, so now we get it for free!
We are well into a regime of diminishing returns, but we'd better start
by seeing if the easy thing works. Besides, setting this to 7 trips the
step limit in _generating_ the algorithm!
Certain existing units in the library require very large prime
numbers---so large, in fact, that our naive trial division hits the
_iteration limit_ for `constexpr` loops. We don't want to force users
to provide a compiler option override, so we'd better find another way.
The solution is to use the "wheel factorization" algorithm:
https://en.wikipedia.org/wiki/Wheel_factorization
This lets us skip most composite numbers in our trial division. The
implementation presented here is configurable in terms of the size of
the "basis" of primes we use. Bigger bases let us skip more primes, but
at the cost of storing more numbers. Fortunately, it turns out that N=3
was good enough for our purposes.
I used `int` before, because it was simple, and I thought _surely_ it
would be enough. I mean, who's going to make a unit whose magnitude has
a prime factor bigger than 32 bits?
This brings us to the "Dalton", a unit whose ratio-magnitude numerator
is 16605390666050. The prime factorization of 16605390666050 is
(2 * 5 * 5 * 53 * 6266185157), and this last number is bigger than 2^31
by a factor of 3 or so.
Fortunately, we should have done this from the beginning anyway, because
otherwise there would be numbers we could represent in `ratio` which we
couldn't represent in `Magnitude`, and this should never be the case.