mirror of
https://github.com/tiennm99/prime-generator.git
synced 2026-10-11 03:13:44 +00:00
feat(go): rewrite in Go
This commit is contained in:
1 parent
fc066c3151
commit
7ffb470db7
7 files changed
+332
-38
No files matched your search
+6
-2
@@ -1,3 +1,7 @@
|
||||
.idea
|
||||
*.iml
|
||||
out
|
||||
|
||||
# Built binary
|
||||
/prime-generator
|
||||
|
||||
# Generated output
|
||||
primes.txt
|
||||
@@ -1,2 +1,57 @@
|
||||
# prime-generator
|
||||
Generate prime numbers from 0 to 1_000_000_000
|
||||
|
||||
Generates every prime from 0 up to a limit with the sieve of Eratosthenes and
|
||||
writes them one per line to a text file. The default limit is 1,000,000,000.
|
||||
|
||||
*Originally written in Java. In 2026 it was rewritten in Go; the Java version is
|
||||
on the [feature/java](https://github.com/tiennm99/prime-generator/tree/feature/java)
|
||||
branch.*
|
||||
|
||||
## Requirements
|
||||
|
||||
- Go 1.25 or higher
|
||||
- Disk space for the output — a full run to 1,000,000,000 writes 50,847,534
|
||||
primes, roughly 500 MB
|
||||
- About 1 GB of RAM at the default limit, since the sieve holds one byte per
|
||||
number
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
go run .
|
||||
```
|
||||
|
||||
Or build a binary first:
|
||||
|
||||
```sh
|
||||
go build -o prime-generator .
|
||||
./prime-generator
|
||||
```
|
||||
|
||||
### Flags
|
||||
|
||||
| Flag | Default | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `-n` | `1000000000` | Upper limit, inclusive, to sieve |
|
||||
| `-out` | `primes.txt` | Output file path |
|
||||
|
||||
```sh
|
||||
# Primes below 100, written somewhere else
|
||||
./prime-generator -n 100 -out small.txt
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
```sh
|
||||
go test ./...
|
||||
```
|
||||
|
||||
The sieve is checked against an independent trial-division implementation, and
|
||||
against the published prime counts at powers of ten up to 1,000,000 — so a bug
|
||||
in the sieve is not mirrored by the thing checking it.
|
||||
|
||||
## Related
|
||||
|
||||
[crawl-prime](https://github.com/tiennm99/crawl-prime) reaches the same output
|
||||
file from the other direction: it downloads a published prime list instead of
|
||||
computing one.
|
||||
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultLimit = 1_000_000_000
|
||||
defaultOutput = "primes.txt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
limit := flag.Int("n", defaultLimit, "upper limit, inclusive, to sieve")
|
||||
out := flag.String("out", defaultOutput, "output file path")
|
||||
flag.Parse()
|
||||
|
||||
f, err := os.Create(*out)
|
||||
if err != nil {
|
||||
log.Fatalf("create %s: %v", *out, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
count, err := writePrimes(f, sieve(*limit))
|
||||
if err != nil {
|
||||
log.Fatalf("write %s: %v", *out, err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
log.Fatalf("close %s: %v", *out, err)
|
||||
}
|
||||
|
||||
fmt.Printf("Wrote %d prime numbers up to %d to %s\n", count, *limit, *out)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// sieve marks primality for every value in [0, limit] with the sieve of
|
||||
// Eratosthenes. Index i holds true when i is prime, so the slice is indexed by
|
||||
// the number itself and positions 0 and 1 stay false.
|
||||
//
|
||||
// A limit below 2 has no primes at all; the nil slice ranges as empty.
|
||||
func sieve(limit int) []bool {
|
||||
if limit < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
isPrime := make([]bool, limit+1)
|
||||
for i := 2; i <= limit; i++ {
|
||||
isPrime[i] = true
|
||||
}
|
||||
|
||||
// Composites below p*p already carry a smaller factor, so crossing out can
|
||||
// start there, and a p past sqrt(limit) has nothing left to mark.
|
||||
for p := 2; p*p <= limit; p++ {
|
||||
if isPrime[p] {
|
||||
for i := p * p; i <= limit; i += p {
|
||||
isPrime[i] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isPrime
|
||||
}
|
||||
|
||||
// writePrimes writes one prime per line, ascending, and reports how many it
|
||||
// wrote. The output is buffered because a full run emits tens of millions of
|
||||
// lines.
|
||||
func writePrimes(w io.Writer, isPrime []bool) (int, error) {
|
||||
bw := bufio.NewWriter(w)
|
||||
|
||||
count := 0
|
||||
for n, prime := range isPrime {
|
||||
if !prime {
|
||||
continue
|
||||
}
|
||||
if _, err := bw.WriteString(strconv.Itoa(n)); err != nil {
|
||||
return count, err
|
||||
}
|
||||
if err := bw.WriteByte('\n'); err != nil {
|
||||
return count, err
|
||||
}
|
||||
count++
|
||||
}
|
||||
|
||||
return count, bw.Flush()
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// primesByTrialDivision is a deliberately naive independent implementation, so
|
||||
// a bug in the sieve is not mirrored by the thing checking it.
|
||||
func primesByTrialDivision(limit int) []int {
|
||||
var primes []int
|
||||
for n := 2; n <= limit; n++ {
|
||||
isPrime := true
|
||||
for d := 2; d*d <= n; d++ {
|
||||
if n%d == 0 {
|
||||
isPrime = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if isPrime {
|
||||
primes = append(primes, n)
|
||||
}
|
||||
}
|
||||
return primes
|
||||
}
|
||||
|
||||
func primesFrom(isPrime []bool) []int {
|
||||
var primes []int
|
||||
for n, prime := range isPrime {
|
||||
if prime {
|
||||
primes = append(primes, n)
|
||||
}
|
||||
}
|
||||
return primes
|
||||
}
|
||||
|
||||
func TestSieveMatchesTrialDivision(t *testing.T) {
|
||||
const limit = 5000
|
||||
|
||||
got := primesFrom(sieve(limit))
|
||||
want := primesByTrialDivision(limit)
|
||||
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("sieve(%d) produced %d primes, trial division produced %d", limit, len(got), len(want))
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Fatalf("sieve(%d) primes[%d] = %d, want %d", limit, i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSieveKnownSmallPrimes(t *testing.T) {
|
||||
want := []int{2, 3, 5, 7, 11, 13, 17, 19, 23, 29}
|
||||
|
||||
got := primesFrom(sieve(30))
|
||||
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("sieve(30) = %v, want %v", got, want)
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("sieve(30)[%d] = %d, want %d", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The prime-counting function at powers of ten is a published, independently
|
||||
// known result, so it pins the sieve at a scale trial division cannot reach.
|
||||
func TestSievePrimeCounts(t *testing.T) {
|
||||
cases := []struct {
|
||||
limit int
|
||||
want int
|
||||
}{
|
||||
{10, 4},
|
||||
{100, 25},
|
||||
{1_000, 168},
|
||||
{10_000, 1_229},
|
||||
{100_000, 9_592},
|
||||
{1_000_000, 78_498},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(strconv.Itoa(tc.limit), func(t *testing.T) {
|
||||
if got := len(primesFrom(sieve(tc.limit))); got != tc.want {
|
||||
t.Errorf("pi(%d) = %d, want %d", tc.limit, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSieveBelowTwoHasNoPrimes(t *testing.T) {
|
||||
for _, limit := range []int{-1, 0, 1} {
|
||||
if got := primesFrom(sieve(limit)); len(got) != 0 {
|
||||
t.Errorf("sieve(%d) = %v, want no primes", limit, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSieveExactlyTwo(t *testing.T) {
|
||||
got := primesFrom(sieve(2))
|
||||
if len(got) != 1 || got[0] != 2 {
|
||||
t.Errorf("sieve(2) = %v, want [2]", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 0 and 1 are not prime, and the slice is indexed by the number itself, so
|
||||
// those positions must stay false rather than shifting every later index.
|
||||
func TestSieveIndexingIsByNumber(t *testing.T) {
|
||||
isPrime := sieve(10)
|
||||
|
||||
if len(isPrime) != 11 {
|
||||
t.Fatalf("len(sieve(10)) = %d, want 11", len(isPrime))
|
||||
}
|
||||
if isPrime[0] || isPrime[1] {
|
||||
t.Error("0 and 1 must not be marked prime")
|
||||
}
|
||||
if !isPrime[7] {
|
||||
t.Error("7 must be marked prime")
|
||||
}
|
||||
if isPrime[9] {
|
||||
t.Error("9 must not be marked prime")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWritePrimesFormat(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
|
||||
count, err := writePrimes(&buf, sieve(20))
|
||||
if err != nil {
|
||||
t.Fatalf("writePrimes returned error: %v", err)
|
||||
}
|
||||
|
||||
const want = "2\n3\n5\n7\n11\n13\n17\n19\n"
|
||||
if buf.String() != want {
|
||||
t.Errorf("output = %q, want %q", buf.String(), want)
|
||||
}
|
||||
if count != 8 {
|
||||
t.Errorf("count = %d, want 8", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWritePrimesEmpty(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
|
||||
count, err := writePrimes(&buf, sieve(1))
|
||||
if err != nil {
|
||||
t.Fatalf("writePrimes returned error: %v", err)
|
||||
}
|
||||
if buf.String() != "" {
|
||||
t.Errorf("output = %q, want empty", buf.String())
|
||||
}
|
||||
if count != 0 {
|
||||
t.Errorf("count = %d, want 0", count)
|
||||
}
|
||||
}
|
||||
|
||||
type errWriter struct{ err error }
|
||||
|
||||
func (w errWriter) Write([]byte) (int, error) { return 0, w.err }
|
||||
|
||||
func TestWritePrimesPropagatesWriteError(t *testing.T) {
|
||||
wantErr := errors.New("disk full")
|
||||
|
||||
// Enough primes to overflow bufio's buffer, so the failure surfaces during
|
||||
// writing rather than only at the final flush.
|
||||
_, err := writePrimes(errWriter{err: wantErr}, sieve(200_000))
|
||||
|
||||
if !errors.Is(err, wantErr) {
|
||||
t.Errorf("writePrimes error = %v, want %v", err, wantErr)
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
int n = 1_000_000_000; // Upper limit
|
||||
boolean[] isPrime = new boolean[n + 1];
|
||||
|
||||
for (int i = 2; i <= n; i++) {
|
||||
isPrime[i] = true;
|
||||
}
|
||||
|
||||
for (int p = 2; p * p <= n; p++) {
|
||||
if (isPrime[p]) {
|
||||
for (int i = p * p; i <= n; i += p) {
|
||||
isPrime[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter("primes.txt"))) {
|
||||
for (int i = 2; i <= n; i++) {
|
||||
if (isPrime[i]) {
|
||||
writer.write(Integer.toString(i));
|
||||
writer.newLine();
|
||||
}
|
||||
}
|
||||
System.out.println("Prime numbers up to " + n + " have been saved to primes.txt.");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user