30个整数映射到1个字节,查表法实现小范围内常数时间素性判定

想用查表法实现小范围内的常数时间的素性判定,怎样节省存储空间呢?自然是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
相关推荐
hez20103 天前
在 .NET 上构建超大托管数组
c#·.net·.net core·gc·clr
用户712122751263 天前
MATLAB 自动化 Excel 转 SLDD 数据字典完整方案(适配自定义 THBPackage 存储类)
matlab
CSharp精选营4 天前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
ZhengEnCi4 天前
P2M-Matplotlib折线图完全指南-从数据可视化到趋势分析的Python绘图利器
python·matlab·数据可视化
ZhengEnCi4 天前
P2L-Matplotlib饼图完全指南-从数据可视化到图表定制的Python绘图利器
python·matlab
刘马想放假7 天前
Modbus 全栈技术解析:TCP、RTU、ASCII、RTU over TCP
数据结构·网络协议
雨落倾城夏未凉8 天前
第四章c#方法-参数数组和可选参数(16)
后端·c#
北域码匠8 天前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
唐青枫9 天前
线程不是越多越快:C#.NET Thread 生命周期、同步与后台工作线程实战
c#·.net
唐青枫10 天前
别只会反射:C#.NET Emit 动态生成代码实战详解
c#·.net