Хорошо, вот биективное отображение от индекса до 5-байтовой строки и обратно на основе Линейного конгруэнтного генератора .
Я использовал константы для drand48, кажется, работает нормально для 40 бит LCG, с семян 1, я проверил все
2 40 значений и полный период.
Он основан на 64-битной математике без знака с замаскированным переполнением, поэтому интенсивно используется NumPy.
Упаковка и распаковка сделаны довольно грубо, много чего нужно улучшить. Не стесняйтесь задавать вопросы.
import numpy as np
# unsigned constants
ZERO = np.uint64(0)
ONE = np.uint64(1)
# signed constants
SZERO = np.int64(0)
SONE = np.int64(1)
MONE = np.int64(-1)
# LCG parameters
bits = np.uint64(40)
mult = np.uint64(25214903917)
incr = np.uint64(11)
mod = np.uint64( np.left_shift(ONE, bits) )
mask = np.uint64(mod - ONE)
def rang(seed: np.uint64) -> np.uint64:
"""
LCG mapping from one 40bit integer to another
"""
return np.uint64(np.bitwise_and(mult*np.uint64(seed) + incr, mask))
def compute_nskip(nskp: np.int64) -> np.int64:
nskip: np.int64 = nskp
while nskip < SZERO:
t: np.uint64 = np.uint64(nskip) + mod
nskip = np.int64(t)
return np.int64( np.bitwise_and(np.uint64(nskip), mask) )
def skip(nskp: np.int64, seed: np.uint64) -> np.uint64: # inverse mapping
"""
Jump from given seed by number of skips
"""
nskip: np.int64 = compute_nskip(nskp)
m: np.uint64 = mult # original multiplicative constant
c: np.uint64 = incr # original additive constant
m_next: np.uint64 = ONE # new effective multiplicative constant
c_next: np.uint64 = ZERO # new effective additive constant
while nskip > SZERO:
if np.bitwise_and(nskip, SONE) != SZERO: # check least significant bit for being 1
m_next = np.bitwise_and(m_next * m, mask)
c_next = np.bitwise_and(c_next * m + c, mask)
c = np.bitwise_and((m + ONE) * c, mask)
m = np.bitwise_and(m * m, mask)
nskip = np.right_shift(nskip, SONE) # shift right, dropping least significant bit
# with G and C, we can now find the new seed
return np.bitwise_and(m_next * seed + c_next, mask)
def index2bytes(i: np.uint64) -> bytes:
bbb: np.uint64 = rang(i)
rc = bytearray()
rc.append( np.uint8( np.bitwise_and(bbb, np.uint64(0xFF)) ) )
bbb = np.right_shift(bbb, np.uint64(8))
rc.append( np.uint8( np.bitwise_and(bbb, np.uint64(0xFF)) ) )
bbb = np.right_shift(bbb, np.uint64(8))
rc.append( np.uint8( np.bitwise_and(bbb, np.uint64(0xFF)) ) )
bbb = np.right_shift(bbb, np.uint64(8))
rc.append( np.uint8( np.bitwise_and(bbb, np.uint64(0xFF)) ) )
bbb = np.right_shift(bbb, np.uint64(8))
rc.append( np.uint8( np.bitwise_and(bbb, np.uint64(0xFF)) ) )
return rc
def bytes2index(a: bytes) -> np.uint64:
seed: np.uint64 = ZERO
seed += np.left_shift( np.uint64(a[0]), np.uint64(0))
seed += np.left_shift( np.uint64(a[1]), np.uint64(8))
seed += np.left_shift( np.uint64(a[2]), np.uint64(16))
seed += np.left_shift( np.uint64(a[3]), np.uint64(24))
seed += np.left_shift( np.uint64(a[4]), np.uint64(32))
return skip(MONE, seed)
# main part, silence overflow warnings first
np.warnings.filterwarnings('ignore')
bbb = index2bytes(ONE)
print(bbb)
idx = bytes2index(bbb)
print(idx)
bbb = index2bytes(999999)
print(bbb)
idx = bytes2index(bbb)
print(idx)
bbb = b'\xa4\x3c\xb1\xfc\x79'
idx = bytes2index(bbb)
print(idx)
bbb = index2bytes(idx)
print(bbb)
print(bbb == b'\xa4\x3c\xb1\xfc\x79')