-
-
Notifications
You must be signed in to change notification settings - Fork 415
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 IsPrime
checker to math
package
#3738
Conversation
Following work by @jemc this now uses a statically allocated table of primes, built via a match table. I also fixed a bug where 1 was being called a prime, which it is not. |
Hi @rhagenson, The changelog - added label was added to this pull request; all PRs with a changelog label need to have release notes included as part of the PR. If you haven't added release notes already, please do. Release notes are added by creating a uniquely named file in the The basic format of the release notes (using markdown) should be:
Thanks. |
match index | ||
| 0 => 2 | 1 => 3 | 2 => 5 | 3 => 7 | 4 => 11 | ||
| 5 => 13 | 6 => 17 | 7 => 19 | 8 => 23 | 9 => 29 | ||
| 10 => 31 | 11 => 37 | 12 => 41 | 13 => 43 | 14 => 47 | ||
| 15 => 53 | 16 => 59 | 17 => 61 | 18 => 67 | 19 => 71 | ||
| 20 => 73 | 21 => 79 | 22 => 83 | 23 => 89 | 24 => 97 | ||
| 25 => 101 | 26 => 103 | 27 => 107 | 28 => 109 | 29 => 113 | ||
| 30 => 127 | 31 => 131 | 32 => 137 | 33 => 139 | 34 => 149 | ||
| 35 => 151 | 36 => 157 | 37 => 163 | 38 => 167 | 39 => 173 | ||
| 40 => 179 | 41 => 181 | 42 => 191 | 43 => 193 | 44 => 197 | ||
| 45 => 199 | 46 => 211 | 47 => 223 | 48 => 227 | 49 => 229 | ||
| 50 => 233 | 51 => 239 | 52 => 241 | 53 => 251 | 54 => 257 | ||
| 55 => 263 | 56 => 269 | 57 => 271 | 58 => 277 | 59 => 281 | ||
| 60 => 283 | 61 => 293 | 62 => 307 | 63 => 311 | 64 => 313 | ||
| 65 => 317 | 66 => 331 | 67 => 337 | 68 => 347 | 69 => 349 | ||
| 70 => 353 | 71 => 359 | 72 => 367 | 73 => 373 | 74 => 379 | ||
| 75 => 383 | 76 => 389 | 77 => 397 | 78 => 401 | 79 => 409 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Theodus thoughts on this vis-a-vis the style guide?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each branch on a separate line would be consistent with the rest of the stdlib. But a match of this size is unusual, so I don't have strong opinions about it.
IsPrime
checker to Math
package
// Using a match table like below provides information to LLVM | ||
// to allocate static globals rather than stack or heap allocation. | ||
fun _low_prime_table_size(): USize => 256 | ||
fun _low_prime_table(index: USize): A val => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be a newline between these methods.
packages/math/is_prime.pony
Outdated
fun apply(n: A): Bool => | ||
var table_index: USize = 0 | ||
while table_index < _low_prime_table_size() do | ||
let prime = _low_prime_table(table_index); table_index = table_index + 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the reason for the ';' here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How Joe had it written. It separates two statements on the same line. I have made them two lines locally and will push with my next commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a dirty multi-statement line writer sometimes! I admit it!
packages/math/is_prime.pony
Outdated
if n == prime then return true end | ||
if (n %% prime) == 0 then return false end | ||
end | ||
if n == 1 then return false end // 1 is not prime |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My preference is for comments like the one at the end of the line to be before the line (i.e. above it rather than trailing)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I blend both trailing and line before comments depending on length of comment. No strong opinion on "right" so I will make the comment be on the previous line.
packages/math/is_prime.pony
Outdated
@@ -0,0 +1,80 @@ | |||
primitive IsPrime[A: (Integer[A] val & Unsigned)] | |||
"""Primality test using 6k+-1 optimization.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anything that can be linked to that explains this optimization?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am trying to find a good source. My search landed me on Wikipedia, but I would prefer something more definitive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The relevant passage is this, but has no source to it.
We can improve this method further. Observe that all primes greater than 3 are of the form 6k ± 1, where k is any integer greater than 0. This is because all integers can be expressed as (6k + i), where i = −1, 0, 1, 2, 3, or 4. Note that 2 divides (6k + 0), (6k + 2), and (6k + 4) and 3 divides (6k + 3). So, a more efficient method is to test whether n is divisible by 2 or 3, then to check through all numbers of the form 6 k ± 1 ≤ n {\displaystyle \scriptstyle 6k\ \pm \ 1\leq {\sqrt {n}}} {\displaystyle \scriptstyle 6k\ \pm \ 1\leq {\sqrt {n}}}. This is 3 times faster than testing all numbers up to √n.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anything is better than nothing! (Maybe) 😉
// Using a match table like below provides information to LLVM | ||
// to allocate static globals rather than stack or heap allocation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this comment be on the _low_primate_table rather than the size function here? Or perhaps right before the table itself?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. It is why I had both methods without a newline as they were sort of acting as one method together. I moved the size method down below the table so the comment could be on top and be above the table method itself.
This is an awesome addition. Thanks @rhagenson. |
@SeanTAllen I got all your comments addressed. I was not able to track down the source of the 6k±1 fact...each place I look simple states it as fact rather than stating where/how it was first formulated. |
I figured out the 6k±1 connection. All integers can be written in as either a multiple of six (i.e., 6k+0) or as being between multiples of six (6k + i, where i is -1, 1, 2, 3, or 4) and because all, but two of those i choices create numbers that are divisible by either 2 or 3 then only those remaining two i choices can encode prime numbers. |
.release-notes/3738.md
Outdated
## Add IsPrime to math package | ||
|
||
Quickly determine if a given number is prime using the 6k ± 1 method. | ||
|
||
All integers (excluding 2 and 3), can be expressed as (6k + i), where i = −1, 0, 1, 2, 3, or 4. Since 2 divides (6k + 0), (6k + 2), and (6k + 4), while 3 divides (6k + 3) that leaves only (6k - 1) and (6k + 1) for expressing primes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. Given that these notes end up in markdown on GitHub. Feel free to do IsPrime
and math
is you want or to use other markdown formatting as well. (You don't have to though)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
It does not have a source because it is (once you realize what it is saying) pretty obviously true. I had taken the 6k±1 to only apply to primes, but applies to all integers -- once I realized that it all fell into place on why it makes sense. |
IsPrime
checker to Math
packageIsPrime
checker to math
package
I am trying to think up a good way to add tests before this is merged. I could reasonable take the 6k+-i formula and verify any such k with i other than +1 and -1 is not prime. I need to put more thought into if that is a good way to test. I also would want to check the various types are correct, ie the use of values inside the static table outside the range of U8 does not miscall when using |
As I suspected, the tests do not pass for all types. I will need to investigate further as to why. Failure on Edit: I saw these failures locally, but wanted to push the changes as I am done for the evening. I will see if they fail in CI as well. |
I think the local failures may be due to some oddities on my local machine. I was playing around with debug builds of |
If the tests all pass, I am going to extend these tests so it is checking more than one value in each range. I will add it as a parameter at the test builder level so we can use it as |
@rhagenson if your PR isn't ready to be merged, go ahead and add a "DO NOT MERGE" label to it so that no one accidentally merges early. And whenever you are ready, go ahead and do a "Squash and merge". Just remember to update the commit comment when squashing. |
I figured out the reason tests were passing is because Edit: Added part about basing number of values to check on bitwidth. |
@@ -63,6 +63,7 @@ actor Main is TestList | |||
itertools.Main.make().tests(test) | |||
json.Main.make().tests(test) | |||
logger.Main.make().tests(test) | |||
math.Main.make().tests(test) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
d'oh! I thought this already existed. dur. I should have realized that it didn't based on the rest of the PR.
Sorry about that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries. Before now, math had no tests so it made sense that this line was not present.
@SeanTAllen Before I squash and merge this, as it is my first such commit to the ponyc repo, any advice/requirements to follow for a good commit message? |
No advice beyond what is in the contributing guide. |
Got it. I will give that another read then merge this. |
Due to some work on roaring-pony which proved to me above my head at this time, I implemented an
IsPrime
checker which might be useful for folks in the future. I am currently working within community members over on Zulip to learn enough LLVM IR to determine if the pre-allocated array of primes is being statically allocated as I intend.