1

I have translated the prime testing code from this paper (here is a link to just the original code) into processing. When testing it I found it works for numbers below 10,000,000 but it skips some primes above that.

Here is my translation (except the table which is identical).

boolean is_SPRP(int n, int a) {
  int d = n-1, s = 0;
  while ((d&1)==0) { s++; d>>>=1; }
  long cur = 1, pw = Integer.toUnsignedLong(d);
  while (pw!=0) {
    if ((pw&1)!=0) { cur = Long.remainderUnsigned((cur*a), n); }
    a = int(Long.remainderUnsigned((long)a*a, n));
    pw >>>= 1;
  }
  if (Long.compareUnsigned(cur, 1)==0) { return(true); }
  for (int r=0; r<s; r++) {
    if (Long.compareUnsigned(cur, n-1)==0) { return(true); }
    cur = Long.remainderUnsigned((cur*cur), n);
  }
  return(false);
}
boolean isPrime(int x) {
  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(x>1); }
  long h = x;
  h = ((h >>> 16) ^ h) * 0x45d9f3b;
  h = ((h >>> 16) ^ h) * 0x45d9f3b;
  h = ((h >>> 16) ^ h) & 255;
  return is_SPRP(x,bases[int(h)]);
}

EDIT: I have found the issue. Processing's int(long) converts to float and then to int which causes rounding errors. Using (int)long fixes the problem. Here is a working (and slightly optimized) version of the code.

int bases[]={15591,2018,166,7429,8064,16045,10503,4399,1949,1295,2776,3620,560,3128,5212,
2657,2300,2021,4652,1471,9336,4018,2398,20462,10277,8028,2213,6219,620,3763,4852,5012,3185,
1333,6227,5298,1074,2391,5113,7061,803,1269,3875,422,751,580,4729,10239,746,2951,556,2206,
3778,481,1522,3476,481,2487,3266,5633,488,3373,6441,3344,17,15105,1490,4154,2036,1882,1813,
467,3307,14042,6371,658,1005,903,737,1887,7447,1888,2848,1784,7559,3400,951,13969,4304,177,41,
19875,3110,13221,8726,571,7043,6943,1199,352,6435,165,1169,3315,978,233,3003,2562,2994,10587,
10030,2377,1902,5354,4447,1555,263,27027,2283,305,669,1912,601,6186,429,1930,14873,1784,1661,
524,3577,236,2360,6146,2850,55637,1753,4178,8466,222,2579,2743,2031,2226,2276,374,2132,813,
23788,1610,4422,5159,1725,3597,3366,14336,579,165,1375,10018,12616,9816,1371,536,1867,10864,
857,2206,5788,434,8085,17618,727,3639,1595,4944,2129,2029,8195,8344,6232,9183,8126,1870,3296,
7455,8947,25017,541,19115,368,566,5674,411,522,1027,8215,2050,6544,10049,614,774,2333,3007,
35201,4706,1152,1785,1028,1540,3743,493,4474,2521,26845,8354,864,18915,5465,2447,42,4511,
1660,166,1249,6259,2553,304,272,7286,73,6554,899,2816,5197,13330,7054,2818,3199,811,922,350,
7514,4452,3449,2663,4708,418,1621,1171,3471,88,11345,412,1559,194};

boolean is_SPRP(int n, int a) {
  int d = (n-1)>>>1, s = 1;
  while ((d&1)==0) { s++; d>>>=1; }
  long cur = 1;
  while (d!=0) {
    if ((d&1)!=0) { cur = Long.remainderUnsigned(cur*a, n); }
    a = (int)Long.remainderUnsigned((long)a*a, n);
    d >>>= 1;
  }
  if (cur==1) { return(true); }
  for (int r=0; r<s; r++) {
    if (cur==n-1) { return(true); }
    cur = Long.remainderUnsigned((cur*cur), n);
  }
  return(false);
}

boolean isPrime(int x) {
  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(x>1); }
  long h = x;
  h = ((h >>> 16) ^ h) * 0x45d9f3b;
  h = ((h >>> 16) ^ h) * 0x45d9f3b;
  h = ((h >>> 16) ^ h) & 255;
  return is_SPRP(x,bases[(int)h]);
}

This version only works for signed integers. Modifying it simply like this for some reason causes the remainder operations to fail.

boolean is_SPRPUnsigned(int n, int a) { //broken (i think)
  int d = (n-1)>>>1, s = 1;
  while ((d&1)==0) { s++; d>>>=1; }
  long cur = 1;
  while (d!=0) {
    if ((d&1)!=0) { cur = Long.remainderUnsigned(cur*a, n); }
    a = (int)Long.remainderUnsigned((long)a*a, n);
    d >>>= 1;
  }
  if (cur==1) { return(true); }
  for (int r=0; r<s; r++) {
    if ((int)cur==n-1) { return(true); }
    cur = Long.remainderUnsigned((cur*cur), n);
  }
  return(false);
}

boolean isPrimeUnsigned(int x) { //not broken (i think)
  if (x==2 || x==3 || x==5 || x==7) { return(true); }
  if (Integer.remainderUnsigned(x, 2)==0 || Integer.remainderUnsigned(x, 3)==0 || Integer.remainderUnsigned(x, 5)==0 || Integer.remainderUnsigned(x, 7)==0) { return(false); }
  if (Integer.compareUnsigned(x, 121)<0) { return(Integer.compareUnsigned(x, 1)>0); }
  long h = Integer.toUnsignedLong(x);
  h = ((h >>> 16) ^ h) * 0x45d9f3b;
  h = ((h >>> 16) ^ h) * 0x45d9f3b;
  h = ((h >>> 16) ^ h) & 255;
  return is_SPRPUnsigned(x,bases[(int)h]);
}

However modifying it further like this fixes that.

boolean is_SPRPUnsigned(int n, int a) {
long ln=Integer.toUnsignedLong(n);
  int d = (n-1)>>>1, s = 1;
  while ((d&1)==0) { s++; d>>>=1; }
  long cur = 1;
  int debug=0;
  while (d!=0) { println(debug); debug++;
    long la=Integer.toUnsignedLong(a);
    if ((d&1)!=0) { cur = Long.remainderUnsigned(cur*la, ln); println("do"); }
    a = (int)Long.remainderUnsigned(la*la, ln);
    d >>>= 1;
    println(cur, a, Integer.toUnsignedString(a));
  }
  if (cur==1) { return(true); }
  for (int r=0; r<s; r++) {
    if ((int)cur==n-1) { return(true); }
    cur = Long.remainderUnsigned((cur*cur), ln);
  }
  return(false);
}

FINAL EDIT: I see now that the error with converting it to use unsigned ints was the in the multiplication. To multiply the int and the long the int must be converted to a long. The conversion is done automatically, but it assumes that it is a signed int. Converting manually prevent this from happening.

cbap
  • 51
  • 1
  • 7
  • Please add the appropriate language tag. – jmargolisvt Jan 16 '21 at 22:01
  • I'm not sure what the original language is, but the language I'm translating to is processing. It's some variant of C though. – cbap Jan 16 '21 at 22:12
  • 2
    You translated `(uint64_t)a` to `(long)a`, but it's not necessarily true that `long` is 64-bit wide. – dxiv Jan 16 '21 at 22:39
  • Thank you for the reply, but that is not the problem (at least I'm 99% sure). I have verified that longs are 64 bits in processing and that when used how I have used them are equivalent to unsigned 64 bit ints. – cbap Jan 17 '21 at 00:35

1 Answers1

1

As @dxiv already pointed out, even though long is 64 bit, it's signed, so it's max value is 9,223,372,036,854,775,807 while uint64's max value is 18,446,744,073,709,551,615, therefore you won't get your expected result for large values.

You could try using BigInteger instead of long:

import java.math.BigInteger;

int bases[]={15591,2018,166,7429,8064,16045,10503,4399,1949,1295,2776,3620,560,3128,5212,
2657,2300,2021,4652,1471,9336,4018,2398,20462,10277,8028,2213,6219,620,3763,4852,5012,3185,
1333,6227,5298,1074,2391,5113,7061,803,1269,3875,422,751,580,4729,10239,746,2951,556,2206,
3778,481,1522,3476,481,2487,3266,5633,488,3373,6441,3344,17,15105,1490,4154,2036,1882,1813,
467,3307,14042,6371,658,1005,903,737,1887,7447,1888,2848,1784,7559,3400,951,13969,4304,177,41,
19875,3110,13221,8726,571,7043,6943,1199,352,6435,165,1169,3315,978,233,3003,2562,2994,10587,
10030,2377,1902,5354,4447,1555,263,27027,2283,305,669,1912,601,6186,429,1930,14873,1784,1661,
524,3577,236,2360,6146,2850,55637,1753,4178,8466,222,2579,2743,2031,2226,2276,374,2132,813,
23788,1610,4422,5159,1725,3597,3366,14336,579,165,1375,10018,12616,9816,1371,536,1867,10864,
857,2206,5788,434,8085,17618,727,3639,1595,4944,2129,2029,8195,8344,6232,9183,8126,1870,3296,
7455,8947,25017,541,19115,368,566,5674,411,522,1027,8215,2050,6544,10049,614,774,2333,3007,
35201,4706,1152,1785,1028,1540,3743,493,4474,2521,26845,8354,864,18915,5465,2447,42,4511,
1660,166,1249,6259,2553,304,272,7286,73,6554,899,2816,5197,13330,7054,2818,3199,811,922,350,
7514,4452,3449,2663,4708,418,1621,1171,3471,88,11345,412,1559,194};  

// 0x45d9f3b as BigInt
final BigInteger HEX_45d9f3b = new BigInteger(new byte[]{(byte)0x04,(byte)0x5d,(byte)0x9f,(byte)0x3b});
final BigInteger HEX_ff = new BigInteger("255");

boolean isSPRP(int n, int a) {
    int d = n-1, s = 0;
    while ((d & 1) == 0) {  
      ++s; 
      d >>= 1;  
    }
    //uint64_t cur = 1, pw = d;
    BigInteger cur = new BigInteger("1");
    BigInteger pw  = new BigInteger(""+d);
    BigInteger abi = new BigInteger(""+a);
    BigInteger nbi = new BigInteger(""+n);
    while (pw.intValue() > 0) { 
        if (pw.and(BigInteger.ONE).intValue() > 0){
          //cur = (cur*a) % n;
          cur = cur.multiply(abi).mod(nbi);
        }
        //a = ((uint64_t)a*a) % n;
        abi = abi.multiply(abi).mod(nbi);
        //pw >>= 1;
        pw = pw.shiftRight(1);
    }   
    if (cur == BigInteger.ONE) return true;
    for (int r=0; r < s; r++) {
        //if (cur == n-1) return true;
        if(cur.equals(nbi.subtract(BigInteger.ONE))){
          return true;
        }
        //cur = (cur*cur) % n;
        cur = cur.multiply(cur).mod(nbi);
    }
    return false;
}       
    
boolean isPrime(int x) { 
    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 (x>1);
    BigInteger h = new BigInteger(""+x);
    //h = ((h >> 16) ^ h) * 0x45d9f3b;
    h = h.shiftRight(16).xor(h).multiply(HEX_45d9f3b);
    //h = ((h >> 16) ^ h) * 0x45d9f3b;
    h = h.shiftRight(16).xor(h).multiply(HEX_45d9f3b);
    //h = ((h >> 16) ^ h) & 255;
    h = h.shiftRight(16).xor(h).and(HEX_ff);
    return isSPRP(x,bases[h.intValue()]);
}   

Note that the above isn't thoroughly tested, so it might actually be buggy. Hopefully it illustrates using BigInt enough to move forward.

It's worth noting that BigInt has isProbablePrime(int) that could be useful.

Additionally, you could wrap the original code using JNI, leaving the implementation/data types intact, simply interfacing/bridging between C++ and Java.

George Profenza
  • 50,687
  • 19
  • 144
  • 218
  • 1
    Thank you for the reply. I have verified that longs are 64 bits in processing and that when used how I have used them are equivalent to unsigned 64 bit ints. I would like to avoid using biginteger because it is slow. I will test the code you've posted to see if the output is the same. – cbap Jan 17 '21 at 00:40
  • I've done a bit of testing and your code seems to work correctly, so it probably is something wrong with how I'm using longs. Hopefully with a bit of crossreferencing I can find where the issue is. – cbap Jan 17 '21 at 00:47
  • My hunch is the the BigInt version of the code might fail, but you should try `isProbablyPrime()`. It feels like making JNI wrapper for the c++ code you've linked above would allow you to use the exact same implementation but in Processing. You will loose a bit of speed through going back and forth between Java / C++, but hopefully this will be fast enough. Alternatively you can use [OpenFrameworks](https://openframeworks.cc/) which is c++ and is similar/inspired by Processing. What do you intend to do with the results once running in Processing ? (e.g. if speed is an issue I'd just use c++) – George Profenza Jan 17 '21 at 00:49
  • Glad to head the code worked correctly. As mentioned in the answer (and so dxiv in the comment) a long isn't big enough to fit all possible uint64 values. For the sake or argument, let's say long is signed and holds values from -127 to 127. (It holds more than that of course, but go with me). Similarly, let's say uint64 holds values from 0-255. The issue, even though long is 64 bit, it's signed (supports both positive and negative values) and needs to use some of it's bits to represent the sign. uint64 is unsigned and because it doesn't support negative numbers it can use all of it's bits... – George Profenza Jan 17 '21 at 00:55
  • ...hence uint64 can store a larger positive value than long can. I don't believe there is a way to use long for the port above. You might be able to work with byte arrays, manually splitting/merging/shifting/etc., however it seems like reinventing the wheel a bit since it would roughly do what BigInt already does. also check [this out](https://stackoverflow.com/questions/25556017/how-to-use-the-unsigned-integer-in-java-8-and-java-9): java 8 and Guava might be interesting options. – George Profenza Jan 17 '21 at 01:05
  • You misunderstand how java handles unsigned ints. In c++ you have to declare an int unsigned for it to be unsigned. In java you declare that you are doing operations on that int as if it were usigned. Because computers use two's compliment encoding, many operations are unaffected but for the affected operations you specify such as with "Long.remainderUnsigned(long1, long2)" – cbap Jan 17 '21 at 01:11
  • I'll need brush up that tbh. So is the `Long.MAX_VALUE` as big as the largest uint64 value in c++ ? – George Profenza Jan 17 '21 at 01:19
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/227428/discussion-between-cbap-and-george-profenza). – cbap Jan 17 '21 at 01:32