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.
The only reason we had the `is_magnitude_integral` type members before
is because the compiler complained about incomplete types. Passing by
`const&` might eliminate this need.
```
test\unit_test\runtime\magnitude_test.cpp(143): error C2672:
'units::check_same_type_and_value': no matching overloaded function
found
```
Maybe it's confused by accessing the static member variable template
using dot-notation on an _instance_? If so, let's see how it likes the
member-function syntax.
I had been using "consteval plus exception" as my way to get
"static_assert, but for parameters". Since consteval doesn't work, then
I can't _guarantee_ that the functions won't be called at runtime.
However, I still think throwing exceptions is better, because it will
cause the desired compiler errors on every configuration. (`assert`
often gets compiled out.)
Very much open to suggestion here.
For each Magnitude `m`, we now support:
- `is_rational(m)` tells whether it represents a rational number.
- `is_integral(m)` tells whether it represents an integer (and
`is_integral(m)` implies `is_rational(m)`).
- `m.value<T>` represents the value of `m` in the type `T`.
If `T` is integral, we only support `m.value<T>` when `is_integral(m)`.
We also perform all intermediate computations in the widest type of the
same category (floating point, signed, or unsigned). This means we can,
for example, give first-class support to embedded users who may have
hardware support only for `float`: we can ensure they get the most
accurate `float` value we can compute, without burdening them with a
dependency on `long double` in their runtime code.
We are not yet ready to replace `ratio` as the definition of unit
magnitudes, but we're a step closer. The next step will be to decompose
a Magnitude into numerator, denominator, and irrational parts, giving us
full bidirectional convertibility with existing `ratio` instances. Then
we can migrate over in a controlled fashion.
This doesn't show up on any other compiler, including MSVC 14.3, so I
think it's just a compiler bug. Cursory googling suggests perhaps that
some older versions of MSVC have immature support for `if constexpr`.
The motivation for the `mag` sub-namespace was to distinguish something
like `mag::product_t<...>` from `dim::product_t<...>`, based on the
idioms of Aurora Units. However, we have no need for a `product_t` type
trait, since we can just use `operator*()`, so we can eliminate this
sub-namespace.
The new test actually passed without modifying the code. However, that
might be implementation-dependent (presumably based on canonicalization
of the `ratio` template parameter), so I wanted a more obviously correct
implementation.
I got my requires expressions/clauses mixed up.
Unfortunately, this means we can't just shove all the "implementation
details" down to the bottom of the file. Oh well.