【问题标题】:Generate unique random numbers in Postgresql with fixed length在 Postgresql 中生成具有固定长度的唯一随机数
【发布时间】:2015-11-17 15:24:17
【问题描述】:

我需要在 Postgresql 中生成固定长度为 13 位的唯一随机数。 我发现了一个类似的thread,其中使用了使用“pseudo_encrypt”加密的序列,但返回的数字不是固定长度。

所以,我需要的是:得到一个固定长度为 13 位的加密随机序列,其中最小值为 0000000000001,最大值为 9999999999999。

有可能吗?如果不可能从前面的零开始不是一个大问题(我认为),我可以在从数据库读取期间以编程方式设置它们,但如果 Postgresql 可以自己做到这一点,那就太好了。

-- 编辑--

在意识到一些有用的事情之后,我必须改变问题以便更好地解释我需要什么:

我需要在 Postgresql 中生成唯一的随机数 (bigint),最大长度固定为 13 位。实际上我正在尝试使用pseudo_encrypt 函数(64 位),但返回的数字显然不是固定的最大长度 13,在 32 位的情况下,最大长度是 10 位(整数),而对于 64位是 19 (bigint)。

那么,如何得到一个最大长度固定为 13 位、最小值为 1、最大值为 9999999999999 的加密随机序列?

是否可以修改 64 位 pseudo_ecrypt 函数以获得此结果?或者如果不可能,还有其他方法可以获得满足此要求的唯一序列吗?

伪加密函数(64 位)

CREATE OR REPLACE FUNCTION pseudo_encrypt(VALUE bigint) returns bigint   AS $$
DECLARE
l1 bigint;
l2 bigint;
r1 bigint;
r2 bigint;
i int:=0;
BEGIN
l1:= (VALUE >> 32) & 4294967295::bigint;
r1:= VALUE & 4294967295;
WHILE i < 3 LOOP
    l2 := r1;
    r2 := l1 # ((((1366.0 * r1 + 150889) % 714025) / 714025.0) * 32767*32767)::int;
    l1 := l2;
    r1 := r2;
    i := i + 1;
END LOOP;
RETURN ((l1::bigint << 32) + r1);
END;
$$ LANGUAGE plpgsql strict immutable;

【问题讨论】:

  • 您可以将该函数的返回值格式化为固定长度:to_char(pseudo_encrypt(nextval('seq')::int), '0000000000000')
  • 那么序列呢?我必须如何设置它?
  • 与您链接到的问题相同。只需确保您的最大值不超过 13 位即可。
  • 好的,我试试,我告诉你,谢谢!
  • 我可以第一个迂腐地声明,如果这些值是唯一的,那么根据定义,它们也不能都是随机的。谢谢。

标签: postgresql random


【解决方案1】:

针对 N

调整 bigint 变体以将输出减少到 2^N 值相对简单,其中 N 是偶数,并且小于 64。

要获得 13 位十进制数字,请考虑 N 的最大值,2^N 有 13 位数字。那是 N=42,2^42=4398046511104。

该算法的工作原理是将输入值分成具有相同位数的两半,并让它们流经 Feistel 网络,本质上是对轮函数的结果进行异或运算,并在每次迭代时交换两半。

如果在流程的每个阶段,每一半都被限制为21 位,那么将两半相结合的结果保证不超过 42 位。

所以这是我提出的变体:

CREATE OR REPLACE FUNCTION pseudo_encrypt42(VALUE bigint) returns bigint
 AS $$
DECLARE
  l1 bigint;
  l2 bigint;
  r1 bigint;
  r2 bigint;
  i int:=0;
  b21 int:=(1<<21)-1; -- 21 bits mask for a half-number => 42 bits total
BEGIN
  l1:= VALUE >> 21;
  r1:= VALUE & b21;
  WHILE i < 3 LOOP
    l2 := r1;
    r2 := l1 # (((((1366*r1+150889)%714025)/714025.0)*32767*32767)::int & b21);
    l1 := l2;
    r1 := r2;
    i := i + 1;
  END LOOP;
  RETURN ((l1::bigint << 21) + r1);
END;
$$ LANGUAGE plpgsql strict immutable;

输入必须小于(2^42)-1,否则输出会发生冲突,如pseudo_encrypt42(x) = pseudo_encrypt42(x mod 2^42)。

2^42 到 10^13 之间的数字缺失怎么办?

2^42 - 10^13 = 5601953488896 所以缺少很多数字。 我不知道如何通过 Feistel 网络一次性提供帮助。不过,一种可能可以接受的解决方法是在 0..M 中生成另一组唯一值并将 2^42 添加到它们,这样就不会有冲突的风险。

这另一个集合可以通过相同的函数获得,只是添加了偏移量。 4398046511104 + pseudo_encrypt42(x) 保证在 4398046511104 和 2*4398046511104 = 8796093022208 唯一值之间,这样更接近目标。相同的技术可以应用于其他几个范围,甚至不一定是相同的大小。

但是,这种解决方法会降低看起来随机的行为,因为不是具有每个数字都可以在 0 和 X 之间的单个输出范围,而是 N X/N 数字的不同输出范围.像这样有几个不同的分区,很容易猜测输出将是哪个分区,而不是分区内的值。

【讨论】:

  • 这解决了我的问题,谢谢丹尼尔!这是一个非常详细的回复,我相信这对其他开发者也将非常有用!
  • 我认为在您的情况下,输入必须小于 (2^21) 以避免冲突,因为您将 VALUE &gt;&gt; (64-21) 用于左侧(就像从左侧取 21 位一样)。在这种情况下,对于大于 2^21 且小于某个值的值,左侧部分仍将为零,但右侧部分将再次从零开始。为了解决这个问题,我使用了VALUE &gt;&gt; 21 左移(就像在前 21 位之后取下 21 位)然后你的限制 (2^42) 变得有效。
  • @mopdobopot:你说得对,很好。我已经修复了答案中的代码。
猜你喜欢
  • 1970-01-01
  • 2020-03-11
  • 1970-01-01
  • 2018-06-19
  • 1970-01-01
  • 1970-01-01
  • 2016-06-17
  • 2012-10-21
  • 2014-03-16
相关资源
最近更新 更多