Work around numbers with very large first factors

We introduce the `known_first_factor` variable template.
This commit is contained in:
Chip Hogg committed 2022-03-19 15:56:12 +00:00
1 parent a99e5f9032
commit 166dd1e944
2 files changed
+48 -2

No files matched your search

+34 -1
View File
@@ -28,6 +28,7 @@
#include <concepts>
#include <cstdint>
#include <numbers>
#include <optional>
#include <stdexcept>
namespace units {
@@ -282,6 +283,20 @@ constexpr bool is_valid_base_power(const BasePower auto& bp)
}
if constexpr (std::is_same_v<decltype(bp.get_base()), std::intmax_t>) {
// Some prime numbers are so big, that we can't check their primality without exhausting limits on constexpr steps
// and/or iterations. We can still _perform_ the factorization for these by using the `known_first_factor`
// workaround. However, we can't _check_ that they are prime, because this workaround depends on the input being
// usable in a constexpr expression. This is true for `prime_factorization` (below), where the input `N` is a
// template parameter, but is not true for our case, where the input `bp.get_base()` is a function parameter. (See
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1045r1.html for some background reading on this
// distinction.)
//
// In our case: we simply give up on excluding every possible ill-formed base power, and settle for catching the
// most likely and common mistakes.
if (const bool too_big_to_check = (bp.get_base() > 1'000'000'000)) {
return true;
}
return is_prime(bp.get_base());
} else {
return bp.get_base() > 0;
@@ -468,12 +483,30 @@ constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * pow<-1
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// `as_magnitude()` implementation.
// Sometimes we need to give the compiler a "shortcut" when factorizing large numbers (specifically, numbers whose
// _first factor_ is very large). If we don't, we can run into limits on the number of constexpr steps or iterations.
//
// To provide the first factor for a given number, specialize this variable template.
//
// WARNING: The program behaviour will be undefined if you provide a wrong answer, so check your math!
template<std::intmax_t N>
inline constexpr std::optional<std::intmax_t> known_first_factor = std::nullopt;
namespace detail {
// Helper to perform prime factorization at compile time.
template<std::intmax_t N>
requires(N > 0)
struct prime_factorization {
static constexpr std::intmax_t first_base = static_cast<std::intmax_t>(Factorizer::find_first_factor(N));
static constexpr std::intmax_t get_or_compute_first_factor()
{
if constexpr (known_first_factor<N>.has_value()) {
return known_first_factor<N>.value();
} else {
return static_cast<std::intmax_t>(Factorizer::find_first_factor(N));
}
}
static constexpr std::intmax_t first_base = get_or_compute_first_factor();
static constexpr std::intmax_t first_power = multiplicity(first_base, N);
static constexpr std::intmax_t remainder = remove_power(first_base, first_power, N);