forked from mpusz/mp-units
Work around numbers with very large first factors
We introduce the `known_first_factor` variable template.
This commit is contained in:
@@ -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);
|
||||
|
||||
|
@@ -28,6 +28,9 @@
|
||||
using namespace units;
|
||||
using namespace units::detail;
|
||||
|
||||
template<>
|
||||
inline constexpr std::optional<std::intmax_t> units::known_first_factor<9223372036854775783> = 9223372036854775783;
|
||||
|
||||
namespace {
|
||||
|
||||
// A set of non-standard bases for testing purposes.
|
||||
@@ -149,7 +152,17 @@ TEST_CASE("make_ratio performs prime factorization correctly")
|
||||
// all odd numbers up to sqrt(N), will exceed this limit for the following prime. Thus, for this test to pass, we
|
||||
// need to be using a more efficient algorithm. (We could increase the limit, but we don't want users to have to
|
||||
// mess with compiler flags just to compile the code.)
|
||||
as_magnitude<334524384739>();
|
||||
as_magnitude<334'524'384'739>();
|
||||
}
|
||||
|
||||
SECTION ("Can bypass computing primes by providing known_first_factor<N>") {
|
||||
// Sometimes, even wheel factorization isn't enough to handle the compilers' limits on constexpr steps and/or
|
||||
// iterations. To work around these cases, we can explicitly provide the correct answer directly to the compiler.
|
||||
//
|
||||
// In this case, we test that we can represent the largest prime that fits in a signed 64-bit int. The reason this
|
||||
// test can pass is that we have provided the answer, by specializing the `known_first_factor` variable template
|
||||
// above in this file.
|
||||
as_magnitude<9'223'372'036'854'775'783>();
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user