При написании программ часто возникает необходимость получить последовательность случайных чисел. В языках высокого уровня существуют штатные функции, а для Ассемблера я использую так называемый "Минимальный генератор Парка-Миллера" (Minimal portable random generator by Park and Miller). От аналогичных алгоритмов его отличает очень малый размер и равномерное распределение получаемых случайных чисел. Математическую модель и описание работы алгоритма можно без труда найти в интернете, поэтому эту информацию я здесь не привожу.
;---------------------------------------------
; Park Miller random number algorithm
; Получить случайное число 0 ... 99999
; stdcall WRandom
; на выходе EAX - случайное число
;---------------------------------------------
proc WRandom
push edx ecx
mov eax,[random_seed]
xor edx,edx
mov ecx,127773
div ecx
mov ecx,eax
mov eax,16807
mul edx
mov edx,ecx
mov ecx,eax
mov eax,2836
mul edx
sub ecx,eax
xor edx,edx
mov eax,ecx
mov [random_seed],ecx
mov ecx,100000
div ecx
mov eax,edx
pop ecx edx
ret
endp
;---------------------------------------------
; Получить случайное число в нужном интервале
; Требуется процедура WRandom
; stdcall WIRandom,min,max
; на выходе EAX - случайное число
;---------------------------------------------
proc WIRandom rmin:dword,rmax:dword
push edx ecx
mov ecx,[rmax]
sub ecx,[rmin]
inc ecx
stdcall WRandom
xor edx,edx
div ecx
mov eax,edx
add eax,[rmin]
pop ecx edx
ret
endp
;---------------------------------------------
; Инициализация генератора случайных чисел
; stdcall WRandomInit
;---------------------------------------------
proc WRandomInit
push eax edx
rdtsc
xor eax,edx
mov [random_seed],eax
pop edx eax
ret
endp
После небольшой доработки он получил возможность генерировать случайные числа в заданном интервале.
Пример использования генератора:
; Сегмент данных
section '.data' data readable writeable
...
random_seed dd 0 ; Переменная для хранения промежуточных результатов
; работы генератора
; Сегмент кода
section '.code' code readable executable
...
; Инициализация генератора
stdcall WRandomInit
; Получить случайное число в интервале 0...99999
stdcall WRandom
; EAX=случайное число
; Получить случайное число в интервале min...max (включая границы)
stdcall WIRandom,5,20
; EAX=случайное число в интервале от 5 до 20
Примечание: процедура инициализации должна обязательно вызываться перед первым обращением к генератору, а переменная random_seed должна находиться в сегменте, доступном для записи. При вызове процедуры WIRandom дополнительных проверок на правильность границ не производится, на их последовательность (условие min < max) - тоже.
UPD: с подачи Дениса Внукова был выявлен серьезный косяк генератора. При нулевом или очень большом (типа 0xFFFFFFFE) значении random_seed генератор намертво зацикливался, возвращая каждый раз 0. Это можно обойти, добавив в WRandom дополнительную проверку значения random_seed. Как побочный положительный момент, генератор становится самоинициализирующимся и больше не требует вызова WRandomInit. Функция WIRandom остается без изменений.
;---------------------------------------------
; Park Miller random number algorithm
; Получить случайное число 0 ... 99999
; stdcall WRandom
; на выходе EAX - случайное число
;---------------------------------------------
proc WRandom
push edx ecx
mov eax,[random_seed]
; Модификация против зацикливания генератора
; на случай если в random_seed будет нулевое значение
or eax,eax
jnz @f
; Инициализация генератора при первом вызове или зацикливании
rdtsc
xor eax,edx
mov [random_seed],eax
@@:
xor edx,edx
mov ecx,127773
div ecx
mov ecx,eax
mov eax,16807
mul edx
mov edx,ecx
mov ecx,eax
mov eax,2836
mul edx
sub ecx,eax
xor edx,edx
mov eax,ecx
mov [random_seed],ecx
mov ecx,100000
div ecx
mov eax,edx
pop ecx edx
ret
endp