Initial commit.
This commit is contained in:
commit
b896fc3b64
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 smariot
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
16
README.md
Normal file
16
README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Golang Prime Numbers Package
|
||||||
|
|
||||||
|
A bunch of helper functions for dealing with small prime numbers that would fit in an unsigned integer.
|
||||||
|
|
||||||
|
This isn't a serious scientifically minded package, or heavily optimized, it's just a quick and dirty tool
|
||||||
|
for grabbing prime numbers should you find yourself needing them.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get smariot.com/prime
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
You can find the documentation at https://pkg.go.dev/smariot.com/prime.
|
475
prime.go
Normal file
475
prime.go
Normal file
@ -0,0 +1,475 @@
|
|||||||
|
// A bunch of helper functions for dealing with small prime numbers that would fit in an unsigned integer.
|
||||||
|
//
|
||||||
|
// This isn't a serious scientifically minded package, or heavily optimized, it's just a quick and dirty tool
|
||||||
|
// for grabbing prime numbers should you find yourself needing them.
|
||||||
|
package prime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"iter"
|
||||||
|
"math"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// initialize nextToCheck to the first odd prime number.
|
||||||
|
nextToCheck uint = 3
|
||||||
|
|
||||||
|
// initialize the list to 2 since this value otherwise can't be reached by incrementing by 2 by an odd starting point.
|
||||||
|
primes = []uint{2}
|
||||||
|
|
||||||
|
// protect the above two variables, which are modified primarily by the functions [upTo] and [firstN].
|
||||||
|
// the test package also has a [resetCache] function to reinitialize them.
|
||||||
|
m sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// this _must_ be larger than 1, because we assume the last item in the list to be odd when we change how switch how we generate these numbers.
|
||||||
|
generateLimit = 1 << 16
|
||||||
|
|
||||||
|
// how many new primes to request at a time.
|
||||||
|
generateStep = 1 << 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// returns a sub-slice of primes that could potentially be a factor of n; primes must contain all the prime numbers up to sqrt(n) in ascending order.
|
||||||
|
func possibleFactors(primes []uint, n uint) []uint {
|
||||||
|
i, found := slices.BinarySearch(primes, uint(math.Sqrt(float64(n))))
|
||||||
|
if found {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return primes[:i:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if n is prime; primes must contain all the prime numbers up to sqrt(n) in ascending order
|
||||||
|
func test(primes []uint, n uint) bool {
|
||||||
|
if len(primes) > 0 && n <= primes[len(primes)-1] {
|
||||||
|
// if the number to test would be inside the list of primes, then check the list using a binary search.
|
||||||
|
_, found := slices.BinarySearch(primes, n)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < 2 {
|
||||||
|
// 0 and 1 won't have any prime factors and would otherwise get classified as prime.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, test if it's prime by checking against its prime factors.
|
||||||
|
for _, factor := range possibleFactors(primes, n) {
|
||||||
|
if n%factor == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a slice containing all the primes up to n inclusive (and possibly more than that).
|
||||||
|
func upTo(n uint) []uint {
|
||||||
|
m.RLock()
|
||||||
|
if n < nextToCheck || nextToCheck == 1 {
|
||||||
|
primes := primes
|
||||||
|
m.RUnlock()
|
||||||
|
return primes
|
||||||
|
}
|
||||||
|
|
||||||
|
m.RUnlock()
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
for ; n >= nextToCheck && nextToCheck != 1; nextToCheck += 2 {
|
||||||
|
if test(primes, nextToCheck) {
|
||||||
|
primes = append(primes, nextToCheck)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return primes
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a slice containing the first n primes (and possibly more than that),
|
||||||
|
// and also possibly less if the nth prime can't fit in a uint.
|
||||||
|
func firstN(n int) []uint {
|
||||||
|
m.RLock()
|
||||||
|
if n <= len(primes) || nextToCheck == 1 {
|
||||||
|
primes := primes
|
||||||
|
m.RUnlock()
|
||||||
|
return primes
|
||||||
|
}
|
||||||
|
|
||||||
|
m.RUnlock()
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
for ; n > len(primes) && nextToCheck != 1; nextToCheck += 2 {
|
||||||
|
if test(primes, nextToCheck) {
|
||||||
|
primes = append(primes, nextToCheck)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return primes
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstN returns a slice containing the first n prime numbers in ascending order.
|
||||||
|
//
|
||||||
|
// The returned slice should be considered read-only and must not be modified.
|
||||||
|
//
|
||||||
|
// The returned slice might contain less than n elements if those numbers wouldn't fit in a uint.
|
||||||
|
func FirstN(n int) []uint {
|
||||||
|
return firstN(n)[:n:n]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test returns true if n is prime.
|
||||||
|
func Test(n uint) bool {
|
||||||
|
return test(upTo(uint(math.Sqrt(float64(n)))), n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the prime number at some relative offset to n, or 0 if there is no applicable number.
|
||||||
|
//
|
||||||
|
// Next(n, 0) will return n if n is prime, otherwise 0.
|
||||||
|
// Next(n, -1) will return the next prime number before n, if one exists (i.e., n >= 3)
|
||||||
|
// Next(n, 1) will return the next prime number after n, if representable in a uint.
|
||||||
|
// Next(n, 100) will return the 100th prime number after n, if representable in a uint.
|
||||||
|
func Next(n uint, offset int) uint {
|
||||||
|
primes := upTo(uint(math.Sqrt(float64(n))))
|
||||||
|
|
||||||
|
// if the list of primes already contains our number (upTo may return more than requested), possibly we can do this based on indexes alone.
|
||||||
|
if primes[len(primes)-1] >= n {
|
||||||
|
i, found := slices.BinarySearch(primes, n)
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
if offset == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
n = primes[i]
|
||||||
|
|
||||||
|
if offset > 0 {
|
||||||
|
offset--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset <= 0 {
|
||||||
|
if i+offset >= 0 && i+offset <= i {
|
||||||
|
return primes[i+offset]
|
||||||
|
}
|
||||||
|
|
||||||
|
// there is definitely nothing before this, stop now.
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// offset > 0
|
||||||
|
if i+offset > i && i+offset < len(primes) {
|
||||||
|
// we know exactly which prime is at this offset.
|
||||||
|
return primes[i+offset]
|
||||||
|
}
|
||||||
|
|
||||||
|
// the list doesn't contain a value for primes[offset+i],
|
||||||
|
// so adjust n to the last prime in the list, and decrease offset by the number of numbers we skipped over.
|
||||||
|
n = primes[len(primes)-1]
|
||||||
|
offset -= len(primes) - 1 - i
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
// is n even?
|
||||||
|
case n%2 == 0:
|
||||||
|
switch {
|
||||||
|
case offset < 0:
|
||||||
|
if n <= 2 {
|
||||||
|
// n=0 or n=2, either way there are no primes before them.
|
||||||
|
// note: this path isn't normally encountered because the the primes list generally always contains
|
||||||
|
// at least 2, and this situation will have been handled via direct index calculation.
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrease to make n odd.
|
||||||
|
n--
|
||||||
|
case offset > 0:
|
||||||
|
// increase to make n odd.
|
||||||
|
n++
|
||||||
|
default:
|
||||||
|
if n == 2 {
|
||||||
|
// the only even prime number.
|
||||||
|
// note: this path isn't normally encountered because the the primes list generally always contains
|
||||||
|
// at least 2, and this situation will have been handled via direct index calculation.
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// some other even number, not prime.
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// if n is already prime, we need to skip over it.
|
||||||
|
case test(primes, n):
|
||||||
|
switch {
|
||||||
|
case offset < 0:
|
||||||
|
switch {
|
||||||
|
case n == 3:
|
||||||
|
if offset == -1 {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fallthrough
|
||||||
|
case n == 2:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
n -= 2
|
||||||
|
case offset > 0:
|
||||||
|
n += 2
|
||||||
|
default:
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// at this point, offset will be signed, and n will be an odd number greater or equal to 3.
|
||||||
|
if offset == 0 || n < 3 || n%2 == 0 {
|
||||||
|
panic(fmt.Sprintf("n=%d offset=%d", n, offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset < 0 {
|
||||||
|
// count backwards
|
||||||
|
|
||||||
|
for ; n != 1; n -= 2 {
|
||||||
|
if test(primes, n) {
|
||||||
|
offset++
|
||||||
|
|
||||||
|
if offset == 0 {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// n=1, meaning n=2 was skipped due to decrementing by 2 each time.
|
||||||
|
// if offset = -1, then return the 2 we skipped.
|
||||||
|
if offset == -1 {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// count forwards
|
||||||
|
listLegalTo := uintSqr(primes[len(primes)-1])
|
||||||
|
|
||||||
|
for ; n != 1; n += 2 {
|
||||||
|
for n > listLegalTo {
|
||||||
|
primes = firstN(len(primes) + generateStep)
|
||||||
|
listLegalTo = uintSqr(primes[len(primes)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if test(primes, n) {
|
||||||
|
offset--
|
||||||
|
|
||||||
|
if offset == 0 {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// integer overflow happened, the remaining offset primes aren't representable via uint.
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpTo returns a slice containing all the up to n inclusive in ascending order.
|
||||||
|
//
|
||||||
|
// The returned slice should be considered read-only and must not be modified.
|
||||||
|
func UpTo(n uint) []uint {
|
||||||
|
primes := upTo(n)
|
||||||
|
// note that we don't add 1 here like we do in [possibleFactors],
|
||||||
|
// as n can be [math.MaxUint] here, which would overflow.
|
||||||
|
// instead, we add 1 if the requested number was found in the list.
|
||||||
|
i, found := slices.BinarySearch(primes, n)
|
||||||
|
if found {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return primes[:i:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// squares a, returning [math.MaxUint] if the operation would overflow.
|
||||||
|
func uintSqr(a uint) uint {
|
||||||
|
if a > 0 && a > math.MaxUint/a {
|
||||||
|
// if squaring would overflow, return MaxUint.
|
||||||
|
return math.MaxUint
|
||||||
|
}
|
||||||
|
|
||||||
|
return a * a
|
||||||
|
}
|
||||||
|
|
||||||
|
// All returns an iterator yielding all the prime numbers up to [math.MaxUint] in ascending order.
|
||||||
|
func All() iter.Seq[uint] {
|
||||||
|
return func(yield func(uint) bool) {
|
||||||
|
var primes []uint
|
||||||
|
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
if i == len(primes) {
|
||||||
|
if i >= generateLimit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
primes = firstN(min(i+generateStep, generateLimit))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !yield(primes[i]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listLegalTo := uintSqr(primes[len(primes)-1])
|
||||||
|
|
||||||
|
// nextCandidate _should_ be odd, so it should be equal to 1 when it overflows.
|
||||||
|
for nextCandidate := primes[len(primes)-1] + 2; nextCandidate != 1; nextCandidate += 2 {
|
||||||
|
if nextCandidate > listLegalTo {
|
||||||
|
// need to grow the list.
|
||||||
|
primes = firstN(len(primes) + generateStep)
|
||||||
|
listLegalTo = uintSqr(primes[len(primes)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if test(primes, nextCandidate) && !yield(nextCandidate) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func factorize(primes []uint, n uint) iter.Seq2[uint, int] {
|
||||||
|
return func(yield func(uint, int) bool) {
|
||||||
|
n := n
|
||||||
|
|
||||||
|
for _, factor := range possibleFactors(primes, n) {
|
||||||
|
exponent := 0
|
||||||
|
|
||||||
|
for n%factor == 0 {
|
||||||
|
n /= factor
|
||||||
|
exponent++
|
||||||
|
}
|
||||||
|
|
||||||
|
if exponent > 0 && !yield(factor, exponent) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if n didn't reach zero, then n is prime.
|
||||||
|
if n > 1 {
|
||||||
|
yield(n, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Factorize returns an iterator that yields the prime factors of n and their exponents.
|
||||||
|
func Factorize(n uint) iter.Seq2[uint, int] {
|
||||||
|
return factorize(upTo(uint(math.Sqrt(float64(n)))), n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LCM computes the least common multiple. This really doesn't have much to do with prime numbers other than the algorithm heavily relying on them.
|
||||||
|
//
|
||||||
|
// Returns 0 if the LCM would overflow, or one of the numbers is 0.
|
||||||
|
func LCM(numbers ...uint) uint {
|
||||||
|
// trivial cases:
|
||||||
|
switch {
|
||||||
|
case len(numbers) == 0:
|
||||||
|
return 1
|
||||||
|
case len(numbers) == 1:
|
||||||
|
return numbers[0]
|
||||||
|
case 0 == slices.Min(numbers):
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
primes := upTo(uint(math.Sqrt(float64(slices.Max(numbers)))))
|
||||||
|
factors := map[uint]int{}
|
||||||
|
|
||||||
|
for _, n := range numbers {
|
||||||
|
for factor, exponent := range factorize(primes, n) {
|
||||||
|
factors[factor] = max(factors[factor], exponent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
product := uint(1)
|
||||||
|
|
||||||
|
for factor, exponent := range factors {
|
||||||
|
for ; exponent > 0; exponent-- {
|
||||||
|
if product > math.MaxUint/factor {
|
||||||
|
// would overflow
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
product *= factor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return product
|
||||||
|
}
|
||||||
|
|
||||||
|
// GCD computes the greatest common divisor. This really doesn't have much to do with prime numbers other than the algorithm heavily relying on them.
|
||||||
|
//
|
||||||
|
// Returns 0 if the list is empty or all of the numbers are 0.
|
||||||
|
func GCD(numbers ...uint) uint {
|
||||||
|
// trivial cases:
|
||||||
|
trivialCases:
|
||||||
|
switch {
|
||||||
|
case len(numbers) == 0:
|
||||||
|
return 0
|
||||||
|
case len(numbers) == 1:
|
||||||
|
return numbers[0]
|
||||||
|
case 0 == slices.Min(numbers):
|
||||||
|
// ugh, why did you have to include a zero?
|
||||||
|
// clone the input slice, sort it, remove duplicates, then remove the first element, which will be the zero
|
||||||
|
// that it taunting us.
|
||||||
|
numbers = slices.Clone(numbers)
|
||||||
|
slices.Sort(numbers)
|
||||||
|
numbers = slices.Compact(numbers)[1:]
|
||||||
|
|
||||||
|
// start over again.
|
||||||
|
goto trivialCases
|
||||||
|
}
|
||||||
|
|
||||||
|
primes := upTo(uint(math.Sqrt(float64(slices.Max(numbers)))))
|
||||||
|
factors := map[uint]int{}
|
||||||
|
var seen []uint
|
||||||
|
|
||||||
|
for i, n := range numbers {
|
||||||
|
seen = seen[:0]
|
||||||
|
|
||||||
|
for factor, exponent := range factorize(primes, n) {
|
||||||
|
if i == 0 {
|
||||||
|
// if this is the first number, store the exponent normally.
|
||||||
|
factors[factor] = exponent
|
||||||
|
} else {
|
||||||
|
seen = append(seen, factor)
|
||||||
|
|
||||||
|
if prevExponent, found := factors[factor]; found {
|
||||||
|
// use the minimum of this exponent and the previously known one.
|
||||||
|
factors[factor] = min(prevExponent, exponent)
|
||||||
|
} else {
|
||||||
|
// the previous numbers must have had an exponent of 0 for this,
|
||||||
|
// and min(0, exponent) would be 0.
|
||||||
|
// we don't need to store factors with an exponent of 0,
|
||||||
|
// so we'd delete it in this case. Except it wasn't found in the map,
|
||||||
|
// so there's nothing to delete.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i != 0 {
|
||||||
|
// if this isn't the first number, and we know about factors that weren't visited,
|
||||||
|
// then those unvisited factors implicitly had an exponent of 0, and need to be deleted.
|
||||||
|
for factor := range factors {
|
||||||
|
// note: factorize returns factors in ascending order, so seen is a sorted list and we
|
||||||
|
// can use a binary search.
|
||||||
|
if _, found := slices.BinarySearch(seen, factor); !found {
|
||||||
|
delete(factors, factor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
product := uint(1)
|
||||||
|
|
||||||
|
for factor, exponent := range factors {
|
||||||
|
for ; exponent > 0; exponent-- {
|
||||||
|
// note: overflow can't happen here.
|
||||||
|
product *= factor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return product
|
||||||
|
}
|
1489
prime_test.go
Normal file
1489
prime_test.go
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user