В закромах Родины нашлась реализация еще одного генератора случайных чисел. Он более громоздкий, чем
минимальный генератор Парка-Миллера, использует операции с плавающей точкой и кольцевой буфер, но результаты выдает гораздо лучше. Кроме большого кода ему также требуется больше места и в сегменте данных:
section '.data' data readable writeable
; Данные для генератора случайных чисел
JJ = 10 ; lag 1
KK = 17 ; lag 2, size of circular buffer
R1 = 19 ; rotate count
R2 = 27 ; rotate count
randp1 dt 1.5 ; used for conversion to float
dw 0 ; alignment
p1 dd 0 ; pointer in circular buffer
p2 dd 0 ; pointer in circular buffer
randbuf dd (2*KK) dup(?) ; circular buffer
Перед первым использованием генератор требуется инициализировать, вызвав функцию WRandomInit с каким-нибудь случайным значением, например, количеством тиков таймера:
; Инициализация генератора случайных чисел
invoke GetTickCount
stdcall WRandomInit,eax
...
После инициализации генератор можно использовать в двух режимах: генерация одиночного случайного числа размером в два двойных слова (функция WRandom) или генерация случайного числа в заданном диапазоне от 1 до 0FFFFFFFEh (функция WIRandom). Формулировка "одиночное случайное число", наверное, не совсем подходит по смыслу к выполняемому действию, но тавтология "произвольное случайное число" мне показалась хуже.
; Получить случайное число
stdcall WRandom
; В регистрах EDX:EAX случайное число
...
; Получить случайное число от 1000 до 100000
stdcall WIRandom,1000,100000
; В регистре EAX случайное число
...
Одиночное случайное число возвращается в паре регистров EDX:EAX, случайное число из заданного интервала возвращается в регистре EAX. Если границы указаны неправильно, то EAX=80000000h. А вот и сам код генератора, данные к нему описаны выше.
;---------------------------------------------
; Получить случайное число
; stdcall WRandom
; на выходе EDX:EAX - случайное число
;---------------------------------------------
proc WRandom
stdcall WBRandom
or edx, 80000000h
mov dword [randp1],eax
mov dword [randp1+4],edx
fld1
fld [randp1]
ret
endp
;---------------------------------------------
; Получить случайное число в нужном интервале
; stdcall WIRandom,min,max
; на выходе EAX - случайное число
; EAX=80000000h - ошибка
;---------------------------------------------
proc WIRandom rmin:dword,rmax:dword
push ebx
stdcall WBRandom
; Проверить корректность задания границ, MIN<MAX
mov ebx,[rmax]
sub ebx,[rmin]
js .WIRandom_1
inc ebx
mov ecx,edx
mul ebx
mov eax,ecx
mov ecx,edx
mul ebx
add eax,ecx
adc edx,[rmin]
mov eax,edx
pop ebx
ret
.WIRandom_1:
mov eax,80000000h
pop ebx
ret
endp
;---------------------------------------------
; Инициализация генератора случайных чисел
; stdcall WRandomInit,seed
;---------------------------------------------
proc WRandomInit seed:dword
mov eax,[seed]
xor ecx,ecx
.WRandomInit_1:
imul eax,2891336453
inc eax
mov [randbuf+ecx*4],eax
inc ecx
cmp ecx,KK*2
JB .WRandomInit_1
fld1
fstp [randp1]
mov [p1],0
mov [p2],JJ*8
stdcall WBRandom
push edi
mov edi,30
.WRandomInit_2:
stdcall WBRandom
dec edi
jnz .WRandomInit_2
pop edi
ret
endp
;---------------------------------------------
; Вспомогательная процедура генератора
; В пользовательском не вызывается
;---------------------------------------------
proc WBRandom
push ebx
mov ebx, [p1]
mov ecx, [p2]
mov edx, [randbuf+ebx]
mov eax, [randbuf+ebx+4]
rol edx, R1
rol eax, R2
add edx, [randbuf+ecx]
add eax, [randbuf+ecx+4]
mov [randbuf+ebx], eax
mov [randbuf+ebx+4], edx
sub ebx, 8
jnc .WBRandom_1
mov ebx, (KK-1)*8
.WBRandom_1:
sub ecx, 8
jnc .WBRandom_2
mov ecx, (KK-1)*8
.WBRandom_2:
mov [p1], ebx
mov [p2], ecx
pop ebx
ret
endp
Этот генератор можно использовать в проектах, где требуется более широкий разброс случайных чисел, нежели выдает генератор Парка-Миллера, и где не так критична минимизация кода.