From 5fb92d2a29ce097efa20b84f2bcd5a8bd5f0c6fc Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Thu, 7 Nov 2024 21:03:56 -0500 Subject: [PATCH 1/6] Implement Strong Lucas test and Baillie-PSW The [Baillie-PSW] is the star of the show: it's fully deterministic for all 64-bit integers, and there are no known counterexamples of any size. In order to reach it, we need to combine the existing Miller-Rabin test with a [Strong Lucas] test, the latter of which is the "hard work" part of this PR. The first step is to find the right value of the "D" parameter to use: the first from the sequence {5, -7, 9, -11, ...} whose Jacobi symbol (see parent PR) is -1. We represent this as an `uint64_t`-and-`bool` struct, rather than a simple `int`, for two reasons. First, it's easier to write this incrementing/alternating pattern with a separate sign. Second, when we use `D.mag` in calculations, it's convenient if it's already a `uint64_t` like almost all of the rest of our types. Oh --- and, this step won't work if `n` is a perfect square, so we add a separate guarding step to filter this situation out. Now for the meat of the calculation. We need to compute elements of the Lucas Sequences, $U_k$ and $V_k$ for certain specific indices. To do that efficiently, we use the fact that $U_1 = V_1 = 1$, and apply the index-doubling and index-incrementing relations: ![Equations for U and V](https://github.com/user-attachments/assets/8cfc1ec9-067e-4c8b-b855-9d86194f4296) The above are derived assuming the Lucas parameters $P=1$, $Q=(1-D)/4$, as is always done for the best-studied parameterization of Baillie-PSW. We use a bit-representation of the desired indices to decide when to double and when to increment, also commonly done (see the Implementation section in the [Strong Lucas] reference). Finally, we are able to combine the new Strong Lucas test with the existing Miller-Rabin test to obtain a working Baillie-PSW test. We use the same kinds of verification strategies for Strong Lucas and Baillie-PSW as we used for Miller-Rabin, except that we don't test pseudoprimes for Baillie-PSW because nobody has ever found any yet. Helps #217. [Baillie-PSW]: https://en.wikipedia.org/wiki/Baillie%E2%80%93PSW_primality_test [Strong Lucas]: https://en.wikipedia.org/wiki/Lucas_pseudoprime#Strong_Lucas_pseudoprimes --- au/code/au/utility/probable_primes.hh | 176 ++++++++++++++++++ .../au/utility/test/probable_primes_test.cc | 104 +++++++++++ 2 files changed, 280 insertions(+) diff --git a/au/code/au/utility/probable_primes.hh b/au/code/au/utility/probable_primes.hh index 0b7c917a..49710538 100644 --- a/au/code/au/utility/probable_primes.hh +++ b/au/code/au/utility/probable_primes.hh @@ -84,6 +84,27 @@ constexpr PrimeResult miller_rabin(std::size_t a, uint64_t n) { return PrimeResult::COMPOSITE; } +// +// Test whether the number is a perfect square. +// +constexpr bool is_perfect_square(uint64_t n) { + if (n < 2u) { + return true; + } + + uint64_t prev = n / 2u; + while (true) { + const uint64_t curr = (prev + n / prev) / 2u; + if (curr * curr == n) { + return true; + } + if (curr >= prev) { + return false; + } + prev = curr; + } +} + constexpr uint64_t gcd(uint64_t a, uint64_t b) { while (b != 0u) { const auto remainder = a % b; @@ -164,5 +185,160 @@ constexpr int jacobi_symbol(int64_t raw_a, uint64_t n) { return jacobi_symbol_positive_numerator(a, n, result); } +struct LucasDParameter { + uint64_t mag = 5u; + bool is_neg = false; + + friend constexpr int as_int(const LucasDParameter &D) { return D.is_neg ? -D.mag : D.mag; } + friend constexpr void increment(LucasDParameter &D) { + D.mag += 2u; + D.is_neg = !D.is_neg; + } +}; + +// +// The first `D` in the infinite sequence {5, -7, 9, -11, ...} whose Jacobi symbol is (-1) is the +// `D` we want to use for the Strong Lucas Probable Prime test. +// +// Requires that `n` is *not* a perfect square. +// +constexpr LucasDParameter find_first_D_with_jacobi_symbol_neg_one(uint64_t n) { + LucasDParameter D{5u, false}; + while (jacobi_symbol(as_int(D), n) != -1) { + increment(D); + } + return D; +} + +// +// Elements of the Lucas sequence. +// +// The default values give the first element (i.e., k=1) of the sequence. +// +struct LucasSequenceElement { + uint64_t U = 1u; + uint64_t V = 1u; +}; + +// Produce the Lucas element whose index is twice the input element's index. +constexpr LucasSequenceElement double_strong_lucas_index(const LucasSequenceElement &element, + uint64_t n, + LucasDParameter D) { + const auto &U = element.U; + const auto &V = element.V; + + uint64_t V_squared = mul_mod(V, V, n); + uint64_t D_U_squared = mul_mod(D.mag, mul_mod(U, U, n), n); + uint64_t V2 = + D.is_neg ? sub_mod(V_squared, D_U_squared, n) : add_mod(V_squared, D_U_squared, n); + V2 = half_mod_odd(V2, n); + + return LucasSequenceElement{ + mul_mod(U, V, n), + V2, + }; +} + +// Find the next element in the Lucas sequence, using parameters for strong Lucas probable primes. +constexpr LucasSequenceElement increment_strong_lucas_index(const LucasSequenceElement &element, + uint64_t n, + LucasDParameter D) { + const auto &U = element.U; + const auto &V = element.V; + + auto U2 = half_mod_odd(add_mod(U, V, n), n); + + const auto D_U = mul_mod(D.mag, U, n); + auto V2 = D.is_neg ? sub_mod(V, D_U, n) : add_mod(V, D_U, n); + V2 = half_mod_odd(V2, n); + + return LucasSequenceElement{U2, V2}; +} + +// Compute the strong Lucas sequence element at index `i`. +constexpr LucasSequenceElement find_strong_lucas_element(uint64_t i, + uint64_t n, + LucasDParameter D) { + LucasSequenceElement element{}; + + bool bits[64] = {}; + std::size_t n_bits = 0u; + while (i > 1u) { + bits[n_bits++] = (i % 2u == 1u); + i /= 2u; + } + + for (std::size_t j = n_bits; j > 0u; --j) { + element = double_strong_lucas_index(element, n, D); + if (bits[j - 1u]) { + element = increment_strong_lucas_index(element, n, D); + } + } + + return element; +} + +// +// Perform a strong Lucas primality test on `n`. +// +constexpr PrimeResult strong_lucas(uint64_t n) { + if (n < 2u || n % 2u == 0u) { + return PrimeResult::BAD_INPUT; + } + + if (is_perfect_square(n)) { + return PrimeResult::COMPOSITE; + } + + const auto D = find_first_D_with_jacobi_symbol_neg_one(n); + + const auto params = decompose(n + 1u); + const auto &s = params.power_of_two; + const auto &d = params.odd_remainder; + + auto element = find_strong_lucas_element(d, n, D); + if (element.U == 0u) { + return PrimeResult::PROBABLY_PRIME; + } + + for (std::size_t i = 0u; i < s; ++i) { + if (element.V == 0u) { + return PrimeResult::PROBABLY_PRIME; + } + element = double_strong_lucas_index(element, n, D); + } + + return PrimeResult::COMPOSITE; +} + +// +// Perform the Baillie-PSW test for primality. +// +// Returns `BAD_INPUT` for any number less than 2, `COMPOSITE` for any larger number that is _known_ +// to be prime, and `PROBABLY_PRIME` for any larger number that is deemed "probably prime", which +// includes all prime numbers. +// +// Actually, the Baillie-PSW test is known to be completely accurate for all 64-bit numbers; +// therefore, since our input type is `uint64_t`, the output will be `PROBABLY_PRIME` if and only if +// the input is prime. +// +constexpr PrimeResult baillie_psw(uint64_t n) { + if (n < 2u) { + return PrimeResult::BAD_INPUT; + } + if (n < 4u) { + return PrimeResult::PROBABLY_PRIME; + } + if (n % 2u == 0u) { + return PrimeResult::COMPOSITE; + } + + if (miller_rabin(2u, n) == PrimeResult::COMPOSITE) { + return PrimeResult::COMPOSITE; + } + + return strong_lucas(n); +} + } // namespace detail } // namespace au diff --git a/au/code/au/utility/test/probable_primes_test.cc b/au/code/au/utility/test/probable_primes_test.cc index 41285c47..f16a14f4 100644 --- a/au/code/au/utility/test/probable_primes_test.cc +++ b/au/code/au/utility/test/probable_primes_test.cc @@ -200,6 +200,110 @@ TEST(MillerRabin, SupportsConstexpr) { static_assert(result == PrimeResult::PROBABLY_PRIME, "997 is prime"); } +TEST(IsPerfectSquare, ProducesCorrectAnswers) { + auto next_sqrt = 0u; + for (auto n = 0u; n < 400'000u; ++n) { + const auto next_square = next_sqrt * next_sqrt; + + const auto is_square = (n == next_square); + if (is_square) { + ++next_sqrt; + } + + EXPECT_EQ(is_perfect_square(n), is_square) << "n = " << n; + } +} + +std::vector strong_lucas_pseudoprimes() { + // https://oeis.org/A217255 + return {5459u, 5777u, 10877u, 16109u, 18971u, 22499u, 24569u, 25199u, 40309u, + 58519u, 75077u, 97439u, 100127u, 113573u, 115639u, 130139u, 155819u, 158399u, + 161027u, 162133u, 176399u, 176471u, 189419u, 192509u, 197801u, 224369u, 230691u, + 231703u, 243629u, 253259u, 268349u, 288919u, 313499u, 324899u}; +} + +TEST(StrongLucas, AllPrimeNumbersAreProbablyPrime) { + const auto primes = first_n_primes<3'000u>(); + for (const auto &p : primes) { + if (p > 2u) { + EXPECT_THAT(strong_lucas(p), Eq(PrimeResult::PROBABLY_PRIME)) << p; + } + } +} + +TEST(StrongLucas, GetsFooledByKnownPseudoprimes) { + for (const auto &p : strong_lucas_pseudoprimes()) { + ASSERT_EQ(miller_rabin(2u, p), PrimeResult::COMPOSITE) << p; + EXPECT_THAT(strong_lucas(p), Eq(PrimeResult::PROBABLY_PRIME)) << p; + } +} + +TEST(StrongLucas, OddNumberIsProbablyPrimeIffPrimeOrPseudoprime) { + const auto primes = first_n_primes<3'000u>(); + const auto pseudoprimes = strong_lucas_pseudoprimes(); + + // Make sure that we are both _into the regime_ of the pseudoprimes, and that we aren't off the + // end of it. + ASSERT_THAT(primes.back(), AllOf(Gt(pseudoprimes.front()), Lt(pseudoprimes.back()))); + + std::size_t i_prime = 1u; // Skip 2; we're only checking odd numbers. + std::size_t i_pseudoprime = 0u; + for (uint64_t n = primes[i_prime]; i_prime < primes.size(); n += 2u) { + const auto is_prime = (n == primes[i_prime]); + if (is_prime) { + ++i_prime; + } + + const auto is_pseudoprime = (n == pseudoprimes[i_pseudoprime]); + if (is_pseudoprime) { + ++i_pseudoprime; + } + + const auto expected = + (is_prime || is_pseudoprime) ? PrimeResult::PROBABLY_PRIME : PrimeResult::COMPOSITE; + EXPECT_THAT(strong_lucas(n), Eq(expected)) << "n = " << n; + } +} + +TEST(BailliePSW, BadInputForLessThanTwo) { + EXPECT_THAT(baillie_psw(0u), Eq(PrimeResult::BAD_INPUT)); + EXPECT_THAT(baillie_psw(1u), Eq(PrimeResult::BAD_INPUT)); +} + +TEST(BailliePSW, TwoIsPrime) { EXPECT_THAT(baillie_psw(2u), Eq(PrimeResult::PROBABLY_PRIME)); } + +TEST(BailliePSW, CorrectlyIdentifiesAllOddNumbersUpToTheFirstThousandPrimes) { + const auto first_10k_primes = first_n_primes<10'000u>(); + + std::size_t i_prime = 1u; // Skip "prime 0" (a.k.a. "2"). + for (uint64_t i = 3u; i_prime < first_10k_primes.size(); i += 2u) { + const bool is_prime = (i == first_10k_primes[i_prime]); + if (is_prime) { + ++i_prime; + } + const auto expected = is_prime ? PrimeResult::PROBABLY_PRIME : PrimeResult::COMPOSITE; + EXPECT_THAT(baillie_psw(i), Eq(expected)) << "i = " << i; + } +} + +TEST(BailliePSW, IdentifiesPerfectSquareAsComposite) { + // (1093 ^ 2 = 1,194,649) is the smallest strong pseudoprime to base 2 that is a perfect square. + constexpr auto n = 1093u * 1093u; + ASSERT_THAT(miller_rabin(2u, n), Eq(PrimeResult::PROBABLY_PRIME)); + EXPECT_THAT(baillie_psw(n), Eq(PrimeResult::COMPOSITE)); +} + +TEST(BailliePSW, HandlesVeryLargePrimes) { + for (const auto &p : { + 225'653'407'801ull, + 334'524'384'739ull, + 9'007'199'254'740'881ull, + 18'446'744'073'709'551'557ull, + }) { + EXPECT_THAT(baillie_psw(p), Eq(PrimeResult::PROBABLY_PRIME)) << p; + } +} + TEST(Gcd, ResultIsAlwaysAFactorAndGCDFindsNoLargerFactor) { for (auto i = 0u; i < 500u; ++i) { for (auto j = 1u; j < i; ++j) { From 24679866fea7635d75b238385ea174bf34c82d94 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Tue, 12 Nov 2024 16:23:39 -0500 Subject: [PATCH 2/6] Be more careful with `as_int` The old version worked fine, but it felt a little sketchy the way we were handling implicit conversion from a wrapped-around unsigned to a smaller signed type. (Note that if the signed type had been bigger, we would silently get the wrong answer!) --- au/code/au/utility/probable_primes.hh | 4 +++- au/code/au/utility/test/probable_primes_test.cc | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/au/code/au/utility/probable_primes.hh b/au/code/au/utility/probable_primes.hh index 49710538..99d6d284 100644 --- a/au/code/au/utility/probable_primes.hh +++ b/au/code/au/utility/probable_primes.hh @@ -189,7 +189,9 @@ struct LucasDParameter { uint64_t mag = 5u; bool is_neg = false; - friend constexpr int as_int(const LucasDParameter &D) { return D.is_neg ? -D.mag : D.mag; } + friend constexpr int as_int(const LucasDParameter &D) { + return bool_sign(!D.is_neg) * static_cast(D.mag); + } friend constexpr void increment(LucasDParameter &D) { D.mag += 2u; D.is_neg = !D.is_neg; diff --git a/au/code/au/utility/test/probable_primes_test.cc b/au/code/au/utility/test/probable_primes_test.cc index f16a14f4..9f167be9 100644 --- a/au/code/au/utility/test/probable_primes_test.cc +++ b/au/code/au/utility/test/probable_primes_test.cc @@ -222,6 +222,11 @@ std::vector strong_lucas_pseudoprimes() { 231703u, 243629u, 253259u, 268349u, 288919u, 313499u, 324899u}; } +TEST(LucasDParameter, CanConvertToInt) { + EXPECT_EQ(as_int(LucasDParameter{5u, false}), 5); + EXPECT_EQ(as_int(LucasDParameter{7u, true}), -7); +} + TEST(StrongLucas, AllPrimeNumbersAreProbablyPrime) { const auto primes = first_n_primes<3'000u>(); for (const auto &p : primes) { From 2fcb8474454f613cde693a63771ad8e6cde47c67 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Tue, 12 Nov 2024 16:29:51 -0500 Subject: [PATCH 3/6] Express D's flag as "is positive" It's harder to reason about a negative. This code just reads better. --- au/code/au/utility/probable_primes.hh | 16 ++++++++++------ au/code/au/utility/test/probable_primes_test.cc | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/au/code/au/utility/probable_primes.hh b/au/code/au/utility/probable_primes.hh index 99d6d284..fb9715fb 100644 --- a/au/code/au/utility/probable_primes.hh +++ b/au/code/au/utility/probable_primes.hh @@ -185,16 +185,20 @@ constexpr int jacobi_symbol(int64_t raw_a, uint64_t n) { return jacobi_symbol_positive_numerator(a, n, result); } +// The "D" parameter in the Strong Lucas probable prime test. +// +// Default construction produces the first value to try according to Selfridge's parameter +// selection. Calling `increment()` on this will successively produce the next parameter to try. struct LucasDParameter { uint64_t mag = 5u; - bool is_neg = false; + bool is_positive = true; friend constexpr int as_int(const LucasDParameter &D) { - return bool_sign(!D.is_neg) * static_cast(D.mag); + return bool_sign(D.is_positive) * static_cast(D.mag); } friend constexpr void increment(LucasDParameter &D) { D.mag += 2u; - D.is_neg = !D.is_neg; + D.is_positive = !D.is_positive; } }; @@ -205,7 +209,7 @@ struct LucasDParameter { // Requires that `n` is *not* a perfect square. // constexpr LucasDParameter find_first_D_with_jacobi_symbol_neg_one(uint64_t n) { - LucasDParameter D{5u, false}; + LucasDParameter D{}; while (jacobi_symbol(as_int(D), n) != -1) { increment(D); } @@ -232,7 +236,7 @@ constexpr LucasSequenceElement double_strong_lucas_index(const LucasSequenceElem uint64_t V_squared = mul_mod(V, V, n); uint64_t D_U_squared = mul_mod(D.mag, mul_mod(U, U, n), n); uint64_t V2 = - D.is_neg ? sub_mod(V_squared, D_U_squared, n) : add_mod(V_squared, D_U_squared, n); + D.is_positive ? add_mod(V_squared, D_U_squared, n) : sub_mod(V_squared, D_U_squared, n); V2 = half_mod_odd(V2, n); return LucasSequenceElement{ @@ -251,7 +255,7 @@ constexpr LucasSequenceElement increment_strong_lucas_index(const LucasSequenceE auto U2 = half_mod_odd(add_mod(U, V, n), n); const auto D_U = mul_mod(D.mag, U, n); - auto V2 = D.is_neg ? sub_mod(V, D_U, n) : add_mod(V, D_U, n); + auto V2 = D.is_positive ? add_mod(V, D_U, n) : sub_mod(V, D_U, n); V2 = half_mod_odd(V2, n); return LucasSequenceElement{U2, V2}; diff --git a/au/code/au/utility/test/probable_primes_test.cc b/au/code/au/utility/test/probable_primes_test.cc index 9f167be9..8f150dd7 100644 --- a/au/code/au/utility/test/probable_primes_test.cc +++ b/au/code/au/utility/test/probable_primes_test.cc @@ -223,8 +223,8 @@ std::vector strong_lucas_pseudoprimes() { } TEST(LucasDParameter, CanConvertToInt) { - EXPECT_EQ(as_int(LucasDParameter{5u, false}), 5); - EXPECT_EQ(as_int(LucasDParameter{7u, true}), -7); + EXPECT_EQ(as_int(LucasDParameter{5u, true}), 5); + EXPECT_EQ(as_int(LucasDParameter{7u, false}), -7); } TEST(StrongLucas, AllPrimeNumbersAreProbablyPrime) { From 8d50da0d90ba31f06ec9ff887817b0ceccba0f7a Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Tue, 12 Nov 2024 21:12:03 -0500 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Michael Hordijk --- au/code/au/utility/test/probable_primes_test.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/au/code/au/utility/test/probable_primes_test.cc b/au/code/au/utility/test/probable_primes_test.cc index 8f150dd7..008c6832 100644 --- a/au/code/au/utility/test/probable_primes_test.cc +++ b/au/code/au/utility/test/probable_primes_test.cc @@ -210,7 +210,7 @@ TEST(IsPerfectSquare, ProducesCorrectAnswers) { ++next_sqrt; } - EXPECT_EQ(is_perfect_square(n), is_square) << "n = " << n; + EXPECT_THAT(is_perfect_square(n), Eq(is_square)) << "n = " << n; } } @@ -223,8 +223,8 @@ std::vector strong_lucas_pseudoprimes() { } TEST(LucasDParameter, CanConvertToInt) { - EXPECT_EQ(as_int(LucasDParameter{5u, true}), 5); - EXPECT_EQ(as_int(LucasDParameter{7u, false}), -7); + EXPECT_THAT(as_int(LucasDParameter{5u, true}), Eq(5)); + EXPECT_THAT(as_int(LucasDParameter{7u, false}), Eq(-7)); } TEST(StrongLucas, AllPrimeNumbersAreProbablyPrime) { @@ -238,7 +238,7 @@ TEST(StrongLucas, AllPrimeNumbersAreProbablyPrime) { TEST(StrongLucas, GetsFooledByKnownPseudoprimes) { for (const auto &p : strong_lucas_pseudoprimes()) { - ASSERT_EQ(miller_rabin(2u, p), PrimeResult::COMPOSITE) << p; + ASSERT_THAT(miller_rabin(2u, p), Eq(PrimeResult::COMPOSITE)) << p; EXPECT_THAT(strong_lucas(p), Eq(PrimeResult::PROBABLY_PRIME)) << p; } } @@ -300,10 +300,10 @@ TEST(BailliePSW, IdentifiesPerfectSquareAsComposite) { TEST(BailliePSW, HandlesVeryLargePrimes) { for (const auto &p : { - 225'653'407'801ull, - 334'524'384'739ull, - 9'007'199'254'740'881ull, - 18'446'744'073'709'551'557ull, + uint64_t{225'653'407'801}, + uint64_t{334'524'384'739}, + uint64_t{9'007'199'254'740'881}, + uint64_t{18'446'744'073'709'551'557}, }) { EXPECT_THAT(baillie_psw(p), Eq(PrimeResult::PROBABLY_PRIME)) << p; } From 1537dd88c588b07179de0e9e22f3c491f2f20818 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Tue, 12 Nov 2024 21:35:57 -0500 Subject: [PATCH 5/6] Use unsigned constants --- au/code/au/utility/test/probable_primes_test.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/au/code/au/utility/test/probable_primes_test.cc b/au/code/au/utility/test/probable_primes_test.cc index 008c6832..441a84e5 100644 --- a/au/code/au/utility/test/probable_primes_test.cc +++ b/au/code/au/utility/test/probable_primes_test.cc @@ -300,10 +300,10 @@ TEST(BailliePSW, IdentifiesPerfectSquareAsComposite) { TEST(BailliePSW, HandlesVeryLargePrimes) { for (const auto &p : { - uint64_t{225'653'407'801}, - uint64_t{334'524'384'739}, - uint64_t{9'007'199'254'740'881}, - uint64_t{18'446'744'073'709'551'557}, + uint64_t{225'653'407'801u}, + uint64_t{334'524'384'739u}, + uint64_t{9'007'199'254'740'881u}, + uint64_t{18'446'744'073'709'551'557u}, }) { EXPECT_THAT(baillie_psw(p), Eq(PrimeResult::PROBABLY_PRIME)) << p; } From b65da1a9b71a30109062a5a07eeb0ac821c1ee61 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Wed, 13 Nov 2024 13:27:37 -0500 Subject: [PATCH 6/6] Use bitmask and shift Co-authored-by: Geoffrey Viola --- au/code/au/utility/probable_primes.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/au/code/au/utility/probable_primes.hh b/au/code/au/utility/probable_primes.hh index fb9715fb..ab7eda8a 100644 --- a/au/code/au/utility/probable_primes.hh +++ b/au/code/au/utility/probable_primes.hh @@ -270,8 +270,8 @@ constexpr LucasSequenceElement find_strong_lucas_element(uint64_t i, bool bits[64] = {}; std::size_t n_bits = 0u; while (i > 1u) { - bits[n_bits++] = (i % 2u == 1u); - i /= 2u; + bits[n_bits++] = (i & 1u); + i >>= 1; } for (std::size_t j = n_bits; j > 0u; --j) {