想用查表法实现小范围内的常数时间的素性判定,怎样节省存储空间呢?自然是1个bit代表1个整数的素性,同时,偶数不用存,个位为5的整数不用存,只有个位为1、3、7、9的整数才可能是素数,也就是每20个整数中只有8个数可能是素数,正好1个字节。
但本文的标题是30个整数映射到1个字节,说明上面的方法还有优化的潜力。
让我们来考虑31~60这30个整数中最多有几个数可能是素数,所有偶数以及个位是5的数都是合数,此时剩余12个数待判定,所有是3的倍数的、个位非5的奇数也都是合数(33,39,51,57),此时正好剩下12-4=8个数待判定,正好1个字节。这个结论对形如[30k+1,30(k+1)]的区间都成立,其中k>0,这是因为30正好是2、3、5的最小公倍数。对于区间[1,30],只有2、3、5这3个小素数是例外(按照上面的说法会被判定为合数),编程时可以单独处理,无伤大雅。
所以,对于任意正整数k,只有30k+(1,7,11,13,17,19,23,29)这8个奇数可能是素数,正好用1个字节存储。
下面是生成这个素性表的MATLAB代码,其中N=256,就是用256Byte存储了256×30=7680个整数的素性。N可以随意调大。
Matlab
clc
N=256;
PrimalityTable=zeros(N,1,'uint8');
for k=1:N
n=(k-1)*30;
a=uint8(0);
if isprime(n+1)
a=bitor(a,uint8(128));
end
if isprime(n+7)
a=bitor(a,uint8(64));
end
%-----------------------
if isprime(n+11)
a=bitor(a,uint8(32));
end
if isprime(n+13)
a=bitor(a,uint8(16));
end
if isprime(n+17)
a=bitor(a,uint8(8));
end
if isprime(n+19)
a=bitor(a,uint8(4));
end
%-----------------------
if isprime(n+23)
a=bitor(a,uint8(2));
end
if isprime(n+29)
a=bitor(a,uint8(1));
end
PrimalityTable(k)=a;
% dec2bin(a)
end
f = fopen('PrimalityTable.txt','w','n','UTF-8');
n_up=floor(N/16);
for n=1:n_up
for k=1:16
fprintf(f,"%3d, ",PrimalityTable((n-1)*16+k));
end
fprintf(f,"\r\n");
end
for k=1:(N-n_up*16)
fprintf(f,"%3d, ",PrimalityTable(n_up*16+k));
end
%以下为正确性检验代码
Map0_29To2n = uint8([0, 128, 0, 0, 0, 0, 0, 64, 0, 0, 0, 32, 0, 16, ...
0, 0, 0, 8, 0, 4, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1]);
for n=1:(N-1)*30
k=floor(n/30);
r=n-k*30;
if xor(bitand(PrimalityTable(k+1), Map0_29To2n(r+1)), isprime(n))
n
end
end
primes(256)
length(primes(N*30))
数组Map0_29To2n的来历如下,本质就是把1,7,11,13,17,19,23,29这些可能为素数的位置挑选出来,
|----|-----|
| 0 | 0 |
| 1 | 128 |
| 2 | 0 |
| 3 | 0 |
| 4 | 0 |
| 5 | 0 |
| 6 | 0 |
| 7 | 64 |
| 8 | 0 |
| 9 | 0 |
| 10 | 0 |
| 11 | 32 |
| 12 | 0 |
| 13 | 16 |
| 14 | 0 |
| 15 | 0 |
| 16 | 0 |
| 17 | 8 |
| 18 | 0 |
| 19 | 4 |
| 20 | 0 |
| 21 | 0 |
| 22 | 0 |
| 23 | 2 |
| 24 | 0 |
| 25 | 0 |
| 26 | 0 |
| 27 | 0 |
| 28 | 0 |
| 29 | 1 |
下面是用C#实现的1~7680内全部整数的常数时间素性判定,超过7680的数,就可以使用试除法或者Miller-Rabin算法进行素性判定。
cs
namespace CSharpIsPrime
{
internal class Program
{
public readonly static byte[] Map0_29To2n = [0, 128, 0, 0, 0, 0, 0, 64, 0, 0, 0, 32, 0, 16,
0, 0, 0, 8, 0, 4, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1];
public readonly static byte[] PrimalityTable = [
127, 251, 247, 126, 109, 219, 188, 159, 171, 242, 120, 207, 87, 101, 183, 121,
103, 48, 203, 203, 220, 187, 154, 165, 86, 230, 73, 189, 30, 120, 101, 106,
106, 199, 181, 180, 123, 84, 50, 170, 155, 197, 15, 249, 192, 42, 133, 31,
116, 191, 34, 151, 102, 111, 200, 92, 29, 50, 212, 92, 162, 136, 253, 42,
49, 131, 94, 205, 19, 61, 49, 242, 132, 26, 142, 142, 217, 131, 232, 247,
42, 105, 88, 16, 167, 193, 49, 98, 78, 223, 117, 166, 73, 241, 26, 225,
75, 73, 27, 129, 166, 100, 199, 5, 136, 28, 227, 100, 60, 129, 215, 153,
177, 138, 17, 124, 36, 207, 204, 178, 90, 209, 56, 229, 84, 45, 26, 50,
114, 100, 111, 152, 65, 59, 193, 195, 52, 143, 28, 64, 173, 179, 179, 64,
77, 82, 41, 48, 234, 50, 94, 12, 194, 208, 143, 211, 34, 54, 36, 31,
152, 128, 169, 21, 58, 206, 87, 177, 36, 105, 212, 10, 101, 68, 120, 35,
139, 18, 96, 43, 92, 244, 46, 57, 224, 86, 160, 17, 253, 22, 168, 116,
6, 170, 199, 237, 138, 25, 16, 40, 97, 90, 85, 162, 178, 146, 14, 228,
75, 201, 171, 83, 213, 64, 193, 134, 160, 36, 115, 225, 68, 67, 149, 181,
24, 49, 178, 30, 139, 145, 104, 13, 234, 227, 70, 69, 3, 44, 36, 74,
117, 90, 2, 76, 177, 132, 16, 194, 44, 109, 75, 109, 155, 152, 135, 6];
static void Main(string[] args)
{
for (int i = 0; i < 256; i++)
{
if (IsPrime(i))
{
Console.Write($"{i}, ");
}
}
Console.WriteLine();
int PrimeCount = 0;
for (int i = 0; i < (PrimalityTable.Length * 30 + 1); i++)
{
if (IsPrime(i))
{
PrimeCount++;
}
}
Console.WriteLine($"PrimeCount: {PrimeCount}");
}
public static bool IsPrime(int n)
{
ArgumentOutOfRangeException.ThrowIfLessThan(n, 0, "n must be greater than or equal to 0.");
bool isPrime = false;
if (n < (PrimalityTable.Length * 30 + 1))
{
switch (n)
{
case 0:
case 1:
return false;
case 2:
case 3:
return true;
case 4:
return false;
case 5:
return true;
default:
if (n % 2 == 0 || n % 3 == 0 || n % 5 == 0)
{
return false;
}
else
{
int quot = n / 30;
int rem = n - quot * 30; // rem = n % 30
if ((PrimalityTable[quot] & Map0_29To2n[rem]) != 0)
{
return true;
}
else
{
return false;
}
}
}
}
else
{
// Miller-Rabin primality test
}
return isPrime;
}
}
}
C#代码的输出结果如下,与MATLAB的验证代码输出结果一致。
cs
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127,
131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193,
197, 199, 211, 223, 227, 229, 233, 239, 241, 251,
PrimeCount: 973