Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add utilities to compute Jacobi symbols #322

Merged
merged 3 commits into from
Nov 12, 2024
Merged

Conversation

chiphogg
Copy link
Contributor

This is a core need for the Baillie-PSW implementation: we will use
Jacobi symbols to set the parameters for the Strong Lucas test.

The Jacobi symbol (a/n) is a multiplicative function of two numbers:
a being any integer, and n any positive odd integer. It only takes
values in {-1, 0, 1}. It's defined in terms of another function,
Legendre symbols, which makes it very intimidating to compute... but
actually, computation is straightforward, because the Jacobi symbol has
symmetries and reduction rules that let us skip computing the Legendre
symbols.

I wrote this implementation by reading the linked wikipedia page, and
following the rules. To test it, I wrote a few manual tests for cases
where the right answer was obvious. I also tested against an
implementation which I found on Wikipedia. I decided not to check the
latter test into version control, because I was unsure about the
licensing implications. However, the Jacobi symbol code will still get
robust testing indirectly in the future, because our Baillie-PSW
implementation will depend on it.

Helps #217.

This is a core need for the Baillie-PSW implementation: we will use
Jacobi symbols to set the parameters for the Strong Lucas test.

The [Jacobi symbol] `(a/n)` is a multiplicative function of two numbers:
`a` being any integer, and `n` any positive odd integer.  It only takes
values in `{-1, 0, 1}`.  It's defined in terms of another function,
Legendre symbols, which makes it very intimidating to compute... but
actually, computation is straightforward, because the Jacobi symbol has
symmetries and reduction rules that let us skip computing the Legendre
symbols.

I wrote this implementation by reading the linked wikipedia page, and
following the rules.  To test it, I wrote a few manual tests for cases
where the right answer was obvious.  I also tested against an
implementation which I found on Wikipedia.  I decided not to check the
latter test into version control, because I was unsure about the
licensing implications.  However, the Jacobi symbol code will still get
robust testing _indirectly_ in the future, because our Baillie-PSW
implementation will depend on it.

Helps #217.

[Jacobi symbol]: https://en.wikipedia.org/wiki/Jacobi_symbol
@chiphogg chiphogg added the release notes: ♻️ lib (refactoring) Under-the-hood changes to library structure label Nov 12, 2024
@chiphogg chiphogg requested a review from geoffviola November 12, 2024 17:59
Copy link
Contributor

@geoffviola geoffviola left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I just left some minor comments.

return a;
}

// The conversions `true` -> `1` and `false` -> `0` are guaranteed by the standard.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It might good to comment that this converts true to 1 and false to -1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

au/code/au/utility/probable_primes.hh Outdated Show resolved Hide resolved
au/code/au/utility/probable_primes.hh Outdated Show resolved Hide resolved
@chiphogg chiphogg merged commit c192a8a into main Nov 12, 2024
13 checks passed
@chiphogg chiphogg deleted the chiphogg/jacobi-symbol#217 branch November 12, 2024 21:13
chiphogg added a commit that referenced this pull request Nov 13, 2024
Formerly, `is_prime` depended on `find_first_factor`.  Now, we reverse
that dependency!  This is good, because while factoring is generally
hard enough that we've built the foundations of global banking on that
difficulty, `is_prime` can be done in polynomial time --- and now is,
because we're using `baillie_psw`.  We have a `static_assert` to make
sure it's restricted to 64 bits, but even this could probably be
removed, because there aren't any known counterexamples of _any_ size.

For efficiency reasons, when factoring a number, it's common to do trial
division before moving on to the "fast" primality checkers.  We hardcode
a list of the "first N primes", taking N=1000 for now.  (We could also
compute them at compile time, but this turned out to have a very large
impact on compilation times.)  It should be easy to adjust the size of
this list if we decide later: there are tests to make sure that it
contains only primes, has them in order, and doesn't skip any.

The new `is_prime` is indeed very fast and accurate.  It now correctly
handles all of the "problem numbers" mentioned in #217, as well as the
(much much larger) largest 64-bit prime.

One more tiny fix: we had switched to use `std::abs` in #322, but this
doesn't actually work: `std::abs` won't be `constexpr` compatible until
C++23!  So as part of this change, we switch back to something that will
work.

Fixes #217.
chiphogg added a commit that referenced this pull request Nov 13, 2024
Formerly, `is_prime` depended on `find_first_factor`.  Now, we reverse
that dependency!  This is good, because while factoring is generally
hard enough that we've built the foundations of global banking on that
difficulty, `is_prime` can be done in polynomial time --- and now is,
because we're using `baillie_psw`.  We have a `static_assert` to make
sure it's restricted to 64 bits, but even this could probably be
removed, because there aren't any known counterexamples of _any_ size.

For efficiency reasons, when factoring a number, it's common to do trial
division before moving on to the "fast" primality checkers.  We hardcode
a list of the "first N primes", taking N=100 for now.  (We could also
compute them at compile time, but this turned out to have a very large
impact on compilation times.)  It should be easy to adjust the size of
this list if we decide later: there are tests to make sure that it
contains only primes, has them in order, and doesn't skip any.

The new `is_prime` is indeed very fast and accurate.  It now correctly
handles all of the "problem numbers" mentioned in #217, as well as the
(much much larger) largest 64-bit prime.

One more tiny fix: we had switched to use `std::abs` in #322, but this
doesn't actually work: `std::abs` won't be `constexpr` compatible until
C++23!  So as part of this change, we switch back to something that will
work.

Fixes #217.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release notes: ♻️ lib (refactoring) Under-the-hood changes to library structure
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants