Skip to content

Commit d7a25b8

Browse files
committed
new modularPower implementation - unfortunately it is too slow
1 parent f57f24e commit d7a25b8

3 files changed

Lines changed: 227 additions & 6 deletions

File tree

src/main/java/de/tilman_neumann/jml/modular/ModularPower.java

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* java-math-library is a Java library focused on number theory, but not necessarily limited to it. It is based on the PSIQS 4.0 factoring project.
3-
* Copyright (C) 2018 Tilman Neumann - tilman.neumann@web.de
3+
* Copyright (C) 2018-2026 Tilman Neumann - tilman.neumann@web.de
44
*
55
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
66
* as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
@@ -17,6 +17,8 @@
1717

1818
import java.math.BigInteger;
1919

20+
import de.tilman_neumann.jml.base.Int128;
21+
2022
/**
2123
* Modular power.
2224
* @author Tilman Neumann
@@ -43,7 +45,46 @@ public class ModularPower {
4345
}
4446
return modPow;
4547
}
48+
49+
/**
50+
* Computes a^b (mod c) for <code>a</code> BigInteger, <code>b, c</code> long.
51+
* This is about 4 times slower than BigInteger.modPow() with the same arguments.
52+
* @param a
53+
* @param b
54+
* @param c
55+
* @return a^b (mod c)
56+
*/
57+
/* not public */ long modPow(BigInteger a, long b, long c) {
58+
return modPowCore128(a.mod(BigInteger.valueOf(c)).longValue(), b, c);
59+
}
60+
61+
/**
62+
* Computes a^b (mod c) for <code>a, b, c</code> long.
63+
* This is about 4 times slower than BigInteger.modPow() with the same arguments.
64+
* @param a
65+
* @param b
66+
* @param c
67+
* @return a^b (mod c)
68+
*/
69+
/* not public */ long modPow(long a, long b, long c) {
70+
return modPowCore128(a % c, b, c);
71+
}
4672

73+
private long modPowCore128(long aModC, long b, long c) {
74+
// if c is long, then the internal products need 128 bit
75+
long modPow = 1;
76+
while (b > 0) {
77+
if ((b&1) == 1) {
78+
Int128 prod = Int128.mul64Unsigned(modPow, aModC);
79+
modPow = Int128.mod128by64Unsigned(prod.getHigh(), prod.getLow(), c);
80+
}
81+
Int128 aModCSquare = Int128.square64Unsigned(aModC);
82+
aModC = Int128.mod128by64Unsigned(aModCSquare.getHigh(), aModCSquare.getLow(), c);
83+
b >>= 1;
84+
}
85+
return modPow;
86+
}
87+
4788
/**
4889
* Computes a^b (mod c) for <code>a</code> BigInteger, <code>b</code> long, <code>c</code> int. Very fast.
4990
* @param a
@@ -52,9 +93,9 @@ public class ModularPower {
5293
* @return a^b (mod c)
5394
*/
5495
public int modPow(BigInteger a, long b, int c) {
55-
return modPowCore(a.mod(BigInteger.valueOf(c)).longValue(), b, c);
96+
return modPowCore64(a.mod(BigInteger.valueOf(c)).longValue(), b, c);
5697
}
57-
98+
5899
/**
59100
* Computes a^b (mod c) for <code>a, b</code> long, <code>c</code> int. Very fast.
60101
* @param a
@@ -63,7 +104,7 @@ public int modPow(BigInteger a, long b, int c) {
63104
* @return a^b (mod c)
64105
*/
65106
public int modPow(long a, long b, int c) {
66-
return modPowCore(a % c, b, c);
107+
return modPowCore64(a % c, b, c);
67108
}
68109

69110
/**
@@ -74,10 +115,10 @@ public int modPow(long a, long b, int c) {
74115
* @return a^b (mod c)
75116
*/
76117
public int modPow(int a, long b, int c) {
77-
return modPowCore(a % c, b, c);
118+
return modPowCore64(a % c, b, c);
78119
}
79120

80-
private int modPowCore(long aModC, long b, long c) {
121+
private int modPowCore64(long aModC, long b, long c) {
81122
long modPow = 1;
82123
while (b > 0) {
83124
if ((b&1) == 1) modPow = (modPow * aModC) % c;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* java-math-library is a Java library focused on number theory, but not necessarily limited to it. It is based on the PSIQS 4.0 factoring project.
3+
* Copyright (C) 2018-2026 Tilman Neumann - tilman.neumann@web.de
4+
*
5+
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
6+
* as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
7+
*
8+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
9+
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10+
*
11+
* You should have received a copy of the GNU General Public License along with this program;
12+
* if not, see <http://www.gnu.org/licenses/>.
13+
*/
14+
package de.tilman_neumann.jml.modular;
15+
16+
import java.math.BigInteger;
17+
import java.util.Random;
18+
19+
import org.apache.logging.log4j.Logger;
20+
import org.apache.logging.log4j.LogManager;
21+
22+
import de.tilman_neumann.util.ConfigUtil;
23+
24+
/**
25+
* Test performance of modular power implementations.
26+
*
27+
* My implementation with long modulus is 4 times slower than BigInteger :-(
28+
* But at least the version with int modulus is faster.
29+
*/
30+
public class ModularPowerPerformanceTest {
31+
private static final Logger LOG = LogManager.getLogger(ModularPowerPerformanceTest.class);
32+
33+
private static final int NCOUNT = 100000;
34+
35+
private static final Random RNG = new Random();
36+
private static final ModularPower modPow = new ModularPower();
37+
38+
private static void testPerformance() {
39+
40+
// set up test numbers
41+
42+
long[] a_arr = new long[NCOUNT];
43+
long[] b_arr = new long[NCOUNT];
44+
long[] c_arr = new long[NCOUNT];
45+
int[] cInt_arr = new int[NCOUNT];
46+
BigInteger[] aBig_arr = new BigInteger[NCOUNT];
47+
BigInteger[] bBig_arr = new BigInteger[NCOUNT];
48+
BigInteger[] cBig_arr = new BigInteger[NCOUNT];
49+
BigInteger[] cIntBig_arr = new BigInteger[NCOUNT];
50+
51+
for (int i=0; i<NCOUNT; i++) {
52+
a_arr[i] = getPositiveRandomLong();
53+
b_arr[i] = getPositiveRandomLong();
54+
c_arr[i] = getPositiveRandomLong();
55+
cInt_arr[i] = (int) (c_arr[i] & Integer.MAX_VALUE);
56+
57+
aBig_arr[i] = BigInteger.valueOf(a_arr[i]);
58+
bBig_arr[i] = BigInteger.valueOf(b_arr[i]);
59+
cBig_arr[i] = BigInteger.valueOf(c_arr[i]);
60+
cIntBig_arr[i] = BigInteger.valueOf(cInt_arr[i]);
61+
}
62+
63+
long t0, t1;
64+
65+
// test BigInteger with long modulus
66+
t0 = System.currentTimeMillis();
67+
for (int i=0; i<NCOUNT; i++) {
68+
aBig_arr[i].modPow(bBig_arr[i], cBig_arr[i]);
69+
}
70+
t1 = System.currentTimeMillis();
71+
LOG.info("BigInteger.modPow(long modulus) took " + (t1-t0) + " ms");
72+
73+
// test my implementation with long modulus
74+
t0 = System.currentTimeMillis();
75+
for (int i=0; i<NCOUNT; i++) {
76+
modPow.modPow(a_arr[i], b_arr[i], c_arr[i]);
77+
}
78+
t1 = System.currentTimeMillis();
79+
LOG.info("ModularPower.modPow(long modulus) took " + (t1-t0) + " ms");
80+
81+
// test BigInteger with int modulus
82+
t0 = System.currentTimeMillis();
83+
for (int i=0; i<NCOUNT; i++) {
84+
aBig_arr[i].modPow(bBig_arr[i], cIntBig_arr[i]);
85+
}
86+
t1 = System.currentTimeMillis();
87+
LOG.info("BigInteger.modPow(long modulus) took " + (t1-t0) + " ms");
88+
89+
// test my implementation with int modulus
90+
t0 = System.currentTimeMillis();
91+
for (int i=0; i<NCOUNT; i++) {
92+
modPow.modPow(a_arr[i], b_arr[i], cInt_arr[i]);
93+
}
94+
t1 = System.currentTimeMillis();
95+
LOG.info("ModularPower.modPow(int modulus) took " + (t1-t0) + " ms");
96+
}
97+
98+
private static long getPositiveRandomLong() {
99+
long n = RNG.nextLong();
100+
while (n == 0) n = RNG.nextLong();
101+
return Math.abs(n);
102+
}
103+
104+
public static void main(String[] args) {
105+
ConfigUtil.initProject();
106+
testPerformance();
107+
}
108+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* java-math-library is a Java library focused on number theory, but not necessarily limited to it. It is based on the PSIQS 4.0 factoring project.
3+
* Copyright (C) 2018-2026 Tilman Neumann - tilman.neumann@web.de
4+
*
5+
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
6+
* as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
7+
*
8+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
9+
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10+
*
11+
* You should have received a copy of the GNU General Public License along with this program;
12+
* if not, see <http://www.gnu.org/licenses/>.
13+
*/
14+
package de.tilman_neumann.jml.modular;
15+
16+
import static org.junit.Assert.assertEquals;
17+
18+
import java.math.BigInteger;
19+
import java.util.Random;
20+
21+
import org.apache.logging.log4j.Logger;
22+
import org.junit.BeforeClass;
23+
import org.junit.Test;
24+
import org.apache.logging.log4j.LogManager;
25+
26+
import de.tilman_neumann.util.ConfigUtil;
27+
28+
public class ModularPowerTest {
29+
private static final Logger LOG = LogManager.getLogger(ModularPowerTest.class);
30+
31+
private static final int NCOUNT = 100000;
32+
33+
private static final Random RNG = new Random();
34+
35+
private static long[] a_arr = new long[NCOUNT];
36+
private static long[] b_arr = new long[NCOUNT];
37+
private static long[] c_arr = new long[NCOUNT];
38+
39+
@BeforeClass
40+
public static void setup() {
41+
ConfigUtil.initProject();
42+
43+
for (int i=0; i<NCOUNT; i++) {
44+
a_arr[i] = getPositiveRandomLong();
45+
b_arr[i] = getPositiveRandomLong();
46+
c_arr[i] = getPositiveRandomLong();
47+
}
48+
}
49+
50+
private static long getPositiveRandomLong() {
51+
long n = RNG.nextLong();
52+
while (n == 0) n = RNG.nextLong();
53+
return Math.abs(n);
54+
}
55+
56+
@Test
57+
public void testModularPowerWithLongModulus() {
58+
ModularPower modPow = new ModularPower();
59+
60+
for (int i=0; i<NCOUNT; i++) {
61+
long a = a_arr[i];
62+
long b = b_arr[i];
63+
long c = c_arr[i];
64+
long p = modPow.modPow(a, b, c);
65+
BigInteger pCorrect = BigInteger.valueOf(a).modPow(BigInteger.valueOf(b), BigInteger.valueOf(c));
66+
if (!pCorrect.equals(BigInteger.valueOf(p))) {
67+
LOG.error("Expected " + a + " ^ " + b + "(mod " + c + ") = " + pCorrect + ", but it was " + p);
68+
}
69+
assertEquals(pCorrect, BigInteger.valueOf(p));
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)