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
相关推荐
逆境不可逃15 分钟前
LeetCode 热题 100 之 33. 搜索旋转排序数组 153. 寻找旋转排序数组中的最小值 4. 寻找两个正序数组的中位数
java·开发语言·数据结构·算法·leetcode·职场和发展
人工智能AI技术1 小时前
DeskClaw Windows上线|C#开发AI桌面助手,轻量内核源码解析
人工智能·c#
leaves falling1 小时前
二分查找:迭代与递归实现全解析
数据结构·算法·leetcode
似水明俊德1 小时前
04-C#.Net-委托和事件-面试题
java·开发语言·面试·c#·.net
杰克尼1 小时前
知识点总结--01
数据结构·算法
freexyn2 小时前
Matlab入门自学七十四:坐标系转换,直角坐标、极坐标和球坐标的转换
开发语言·算法·matlab
咱就是说不配啊2 小时前
3.20打卡day34
数据结构·c++·算法
cui_ruicheng3 小时前
C++数据结构进阶:哈希表实现
数据结构·c++·算法·哈希算法·散列表
光电笑映3 小时前
高阶数据结构之红黑树详解
数据结构
逆境不可逃4 小时前
LeetCode 热题 100 之 35. 搜索插入位置 74. 搜索二维矩阵 34. 在排序数组中查找元素的第一个和最后一个位置
数据结构·算法·leetcode