Skip to content

Commit

Permalink
Merge pull request #4 from kspalaiologos/wip-prime
Browse files Browse the repository at this point in the history
Basic primality tests.
  • Loading branch information
kspalaiologos authored Jan 14, 2024
2 parents 168d5eb + 38c7b99 commit 146a987
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 0 deletions.
49 changes: 49 additions & 0 deletions src/main/java/rocks/palaiologos/maja/Maja.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package rocks.palaiologos.maja;

import rocks.palaiologos.maja.structure.AdditiveGroup;
import rocks.palaiologos.maja.structure.AdditiveGroupoid;
import rocks.palaiologos.maja.structure.MultiplicativeGroup;
import rocks.palaiologos.maja.structure.MultiplicativeGroupoid;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Random;
Expand Down Expand Up @@ -196,6 +201,13 @@ public static double add(double x, double y) {
return x + y;
}

/**
* Adds two values through an additive groupoid.
*/
public static <T> T add(AdditiveGroupoid<T> groupoid, T x, T y) {
return groupoid.plus(x, y);
}

/**
* Subtracts two double precision numbers.
*
Expand All @@ -207,6 +219,13 @@ public static double sub(double x, double y) {
return x - y;
}

/**
* Subtracts two values through an additive group.
*/
public static <T> T add(AdditiveGroup<T> group, T x, T y) {
return group.plus(x, group.addInv(y));
}

/**
* Multiplies two double precision numbers.
*
Expand All @@ -218,6 +237,13 @@ public static double mul(double x, double y) {
return x * y;
}

/**
* Multiplies two values through a multiplicative groupoid.
*/
public static <T> T mul(MultiplicativeGroupoid<T> groupoid, T x, T y) {
return groupoid.dot(x, y);
}

/**
* Divides two double precision numbers.
*
Expand All @@ -229,6 +255,13 @@ public static double div(double x, double y) {
return x / y;
}

/**
* Divides two values through a multiplicative group.
*/
public static <T> T mul(MultiplicativeGroup<T> group, T x, T y) {
return group.dot(x, group.mulInv(y));
}

/**
* Returns the modulus of two double precision numbers.
*
Expand Down Expand Up @@ -4858,4 +4891,20 @@ public static double integrateGaussLegendreReal(BiFunction<Double, Double, Doubl
public static Complex integrateGaussLegendreComplex(BiFunction<Complex, Complex, Complex> f, Complex a, Complex b, Complex c, Complex d, int N) {
return integrateGaussLegendreComplex(x -> integrateGaussLegendreComplex(y -> f.apply(x, y), c, d, N), a, b, N);
}

/**
* Primality test for an integer in range [0, 2^31-1].
*/
public static boolean isPrime(int n) {
return Prime.is_prime_1b(n);
}

/**
* Primality test for an unsigned integer in range [0, 2^64-1].
* Notice that some values of n that will be interpreted as negative when signed
* may yield a truthy value, as the number is reinterpreted as unsigned.
*/
public static boolean isPrime(long n) {
return Prime.is_prime_2_64(n);
}
}
179 changes: 179 additions & 0 deletions src/main/java/rocks/palaiologos/maja/Prime.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package rocks.palaiologos.maja;

class Prime {
// Fast Primality Testing for Integers That Fit into a Machine Word
// Michal Forisek and Jakub Jancina
// All codes published under https://people.ksp.sk/~misof/primes/
// are available under the CC BY-NC 4.0 Int'l license.
// [https://creativecommons.org/licenses/by-nc/4.0/legalcode]

public static long pow(long a, long e, long n) {
long ans = 1;
while (e > 0) {
if (e % 2 == 1) ans = (ans * a) % n;
a = (a * a) % n;
e >>= 1;
}
return ans;
}

public static boolean is_SPRP(long a, long n) {
int d = 0;
long t = n - 1;
while (t % 2 == 0) {
++d;
t >>= 1;
}
long x = pow(a, t, n);
if (x == 1) return true;
for (int i = 0; i < d; ++i) {
if (x == n - 1) return true;
x = (x * x) % n;
if (x == 1) return false;
}
return false;
}
public static boolean is_prime_1b(int x) {
if (x < 2) return false;
if (x == 2 || x == 3 || x == 5 || x == 7) return true;
if (x % 2 == 0 || x % 3 == 0 || x % 5 == 0 || x % 7 == 0) return false;
if (x < 121) return true;
return is_SPRP(PrimeConstantPool.bases_2[(int) (((0xAFF7B4L * x) >> 7) & 1023)], x);
}

static class MontgomeryResult {
public long u, v;
public MontgomeryResult(long u, long v) {
this.u = u; this.v = v;
}
}

public static void xbinGCD(long a, long b, MontgomeryResult r) {
long alpha, beta, u, v;
u = 1; v = 0;
alpha = a; beta = b;

while (Long.compareUnsigned(a, 0) > 0) {
a = a >>> 1;
if ((u & 1) == 0) {
u = u >>> 1; v = v >>> 1;
} else {
u = ((u ^ beta) >>> 1) + (u & beta);
v = (v >>> 1) + alpha;
}
}

r.u = u; r.v = v;
}


public static long modul64(long x, long y, long z) {
long i, t;

for (i = 1; i <= 64; i++) {
t = x >>> 63;
x = (x << 1) | (y >>> 63);
y = y << 1;
if (Long.compareUnsigned(x | t, z) >= 0) {
x = x - z;
y = y + 1;
}
}
return x;
}

public static void mulul64(long u, long v, MontgomeryResult r) {
long u0, u1, v0, v1, k, t;
long w0, w1, w2;

u1 = u >>> 32; u0 = u & 0xFFFFFFFFL;
v1 = v >>> 32; v0 = v & 0xFFFFFFFFL;

t = u0*v0;
w0 = t & 0xFFFFFFFFL;
k = t >>> 32;

t = u1*v0 + k;
w1 = t & 0xFFFFFFFFL;
w2 = t >>> 32;

t = u0*v1 + w1;
k = t >>> 32;

long wlo = (t << 32) + w0;
long whi = u1*v1 + w2 + k;

r.u = wlo; r.v = whi;
}

public static long montmul(long abar, long bbar, long m, long mprime, MontgomeryResult r) {
long thi, tlo, tm, tmmhi, tmmlo, uhi, ulo; boolean ov;

mulul64(abar, bbar, r); thi = r.v; tlo = r.u;

tm = tlo*mprime;

mulul64(tm, m, r); tmmhi = r.v; tmmlo = r.u;

ulo = tlo + tmmlo;
uhi = thi + tmmhi;
if (Long.compareUnsigned(ulo, tlo) < 0) uhi = uhi + 1;

ov = (Long.compareUnsigned(uhi, thi) < 0) || ((Long.compareUnsigned(uhi, thi) == 0) && (Long.compareUnsigned(ulo, tlo) < 0));

ulo = uhi;
uhi = 0;

if (ov || Long.compareUnsigned(ulo, m) >= 0)
ulo = ulo - m;

return ulo;
}

public static long mulmodMont(long baseM,long e,long modul,long pv,long oneM, MontgomeryResult r) {
long ans = oneM;
while(Long.compareUnsigned(e, 0) > 0) {
if((e&1) == 1)
ans = montmul(baseM,ans,modul,pv,r);
baseM = montmul(baseM,baseM,modul,pv,r);
e>>>=1;
}
return ans;
}

public static boolean is_SPRP(long base,long modul, MontgomeryResult r) {
if(Long.compareUnsigned(base,modul)>=0) base=Long.remainderUnsigned(base, modul);
long pu,pv;
xbinGCD(1l<<63l,modul,r);
pu = r.u; pv = r.v;
long baseM = modul64(base,0,modul);
long oneM = modul64(1,0,modul);
long moneM = modul - oneM;
long e = modul-1;
while(Long.compareUnsigned(e&1,0)==0) e>>>=1;
long t = mulmodMont(baseM,e,modul,pv,oneM,r);
if(t==oneM) return true;
while(Long.compareUnsigned(e,modul-1)<0) {
if(Long.compareUnsigned(t,moneM)==0) return true;
t = montmul(t,t,modul,pv,r);
if(Long.compareUnsigned(t,oneM)==0) return false;
e<<=1;
}
return false;
}

public static int hashh(long x) {
x = ((x >>> 32) ^ x) * 0x45d9f3b3335b369l;
x = ((x >>> 32) ^ x) * 0x3335b36945d9f3bl;
x = ((x >>> 32) ^ x);
return (int) (x & 262143l);
}

public static boolean is_prime_2_64(long a) {
MontgomeryResult r = new MontgomeryResult(0, 0);
if (Long.compareUnsigned(a,2)==0 || Long.compareUnsigned(a,3)==0 || Long.compareUnsigned(a,5)==0 || Long.compareUnsigned(a,7)==0) return true;
if (Long.remainderUnsigned(a,2)==0 || Long.remainderUnsigned(a,3)==0 || Long.remainderUnsigned(a,5)==0 || Long.remainderUnsigned(a,7)==0) return false;
if (Long.compareUnsigned(a,121)<0) return Long.compareUnsigned(a,1) > 0;
return is_SPRP(2,a,r) && is_SPRP(PrimeConstantPool.bases[hashh(a)],a,r);
}
}
39 changes: 39 additions & 0 deletions src/main/java/rocks/palaiologos/maja/PrimeConstantPool.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package rocks.palaiologos.maja;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class PrimeConstantPool {
public static short[] bases_2;
public static long[] bases;

static {
// Read the lines of the file "maja_bases.txt" from classpath
// and parse them into the arrays bases_2 and bases.
try {
InputStream in = PrimeConstantPool.class.getClassLoader().getResourceAsStream("maja_bases.txt");
byte[] data = new byte[1024 * 1024];
int n = in.read(data);
String[] lines = new String(data, 0, n, StandardCharsets.US_ASCII).split(System.lineSeparator());
String[] bases_2_str = lines[0].split(",");
short[] bases_2 = new short[bases_2_str.length];
for (int i = 0; i < bases_2_str.length; ++i) {
bases_2[i] = Short.parseShort(bases_2_str[i]);
}
PrimeConstantPool.bases_2 = bases_2;
String[] bases_str = lines[1].split(",");
long[] bases = new long[bases_str.length];
for (int i = 0; i < bases_str.length; ++i) {
bases[i] = Long.parseUnsignedLong(bases_str[i]);
}
PrimeConstantPool.bases = bases;
in.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
2 changes: 2 additions & 0 deletions src/main/resources/maja_bases.txt

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions src/test/java/TestArithmetic.java
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,10 @@ public void testNChooseK() {
}
}
}

@Test
public void testPrime() {
for (int i = 0; i < 1000000; i++)
assertEquals(Maja.isPrime(i), Maja.isPrime((long) i));
}
}

0 comments on commit 146a987

Please sign in to comment.