Tvůrce webu je i pro tebe! Postav třeba web. Bez grafika. Bez kodéra. Hned.
wz

Obsah / Utility / RANDOM / Náhodné číslo s danou maximální hodnotou

Zdrojový kód: INCLUDE\UTIL\RANDOM.INC, UTIL\RANDOM.ASM


Náhodné číslo s danou maximální hodnotou

Náhodná čísla s danou maximální hodnotou jsou obvykle počítána tak, že náhodně vygenerované číslo se vydělí požadovaným rozsahem hodnot a výsledek po dělení se použije jako náhodné číslo s danou maximální hodnotou (s číslem provedeme tedy operaci modulo). Tato metoda má však jednu velkou nectnost - nepravidelný výskyt rozložení pravděpodobnosti.

Představme si fiktivní situaci, kdy máme k dispozici lineární generátor s rozsahem hodnot 0 až 7. Provedli jsme 800 pokusů a každá z 8 hodnot byla vygenerována 100x. Z vygenerovaných čísel chceme vytvořit náhodná čísla s rozsahem hodnot 0 až 4 (tedy 5 hodnot). Případnou operaci s dělením si můžeme představit tak, jako bychom sloupec možných hodnot obtočili kolem trubky s šířkou rovnou děliteli (tj. od čísla opakovaně odečítáme dělitele, až se číslo dostane do požadovaných mezí). V tomto případě zůstane poslední otáčka neúplná a jak vidíme na obrázku, jednotlivá čísla nemají stejnou pravděpodobnost výskytu (čísla 0 až 2 mají mnohem větší četnost, 200x).

Náprava spočívá v jiné metodě generování náhodných čísel v intervalu. Náhodné číslo vygenerované lineárním generátorem náhody zkontrolujeme, zda spadá do požadovaného rozsahu a pokud neodpovídá, zahodíme ho a vygenerujeme nové číslo. Tím je zajištěno rovnoměrné rozložení četnosti čísel nezávisle na požadovaném rozsahu čísel.

Pro zvýšení efektivnosti generátoru náhody, aby nedocházelo při malých intervalech k příliš častému odhazování nevyhovujících čísel, je náhodné číslo nejdříve maskováno binární maskou tak, aby se maskované číslo rozsahem co nejvíce blížilo požadovanému rozsahu čísel. Náhodné číslo si i po zamaskování binární maskou zachovává lineární rozložení četnosti. Ke stanovení binární masky je s výhodou využita instrukce BSR, která umí rychle vyhledat pozici nejvyššího bitu čísla.


; -----------------------------------------------------------------------------
;                Get random unsigned byte with max value
; -----------------------------------------------------------------------------
; INPUT:	AL = maximal unsigned value (0 to 255)
; OUTPUT:	AL = random byte (0 to maximal value)
; -----------------------------------------------------------------------------

; ------------- Check maximal value 0

RandMaxByte:	or	al,al		; zero maximal value?
		jz	RandMaxByte8	; maximal value is 0

; ------------- Push registers

		push	ecx		; push ECX
		push	edx		; push EDX

; ------------- Prepare mask of maximal value

		movzx	edx,al		; EDX <- maximal value
		bsr	ecx,edx		; ECX <- bits of maximal value
		mov	ch,al		; CH <- maximal value
		mov	dl,B1		; DL <- maximal value bit
		shl	edx,cl		; EDX <- maximal value
		dec	edx		; DL <- mask of result

; ------------- Find required value

RandMaxByte2:	call	RandByte	; get random byte (-> AL)
		and	al,dl		; mask result
		cmp	al,ch		; is this value allowed?
		ja	RandMaxByte2	; invalid value, next attempt

; ------------- Pop registers

		pop	edx		; pop EDX
		pop	ecx		; pop ECX
RandMaxByte8:	ret

Funkce RandMaxByte vygeneruje náhodné číslo "bajt bez znaménka" s rozsahem 0 až dané maximum MAX (včetně nuly a maximální hodnoty), které funkci předáme v registru AL. Maximum může mít rozsah hodnot 0 až 255. Výsledné číslo obdržíme v registru AL.

Na začátku funkce nejdříve otestujeme případ, kdy je požadována maximální hodnota 0. V tom případě se funkce ukončí okamžitě, protože zadané maximum 0 je současně jediným možným generovaným číslem.

Funkce používá jako pracovní registry ECX a EDX, jejich obsahy jsou proto uchovány pomocí instrukcí PUSH/POP.

Před samotným generováním čísla se připraví maska maximální hodnoty. Do registru EDX se načte z registru AL maximální požadovaná hodnota čísla (instrukce MOVZX zaplní zbytek registru EDX nulami) a instrukcí BSR se vyhledá pozice nejvyššího bitu "1" v registru EDX. Nalezená bitová pozice se uloží do registru ECX.

Maximální požadovaná hodnota čísla se uchová do registru CH pro pozdější použití. Do registru DL se připraví hodnota 2 (tj. bit 1) a rotací doleva o zjištěný počet bitů a dekrementací výsledku se vygeneruje maska maximální hodnoty čísla. Například pro maximální hodnotu 15h (=21) bude pozice nejvyššího bitu 4, maximální hodnota pro masku bude 2<<4 = 2*10h = 20h (=32) a maska náhodného čísla bude 1Fh (=31).

Nyní se již může přistoupit ke generování náhodného čísla. Pomocí funkce RandByte se vygeneruje do registru AL náhodné číslo s rozsahem 0 až 255. Číslo se zamaskuje maskou z registru DL a výsledek se porovná s požadovaným maximem v registru CH. Je-li vygenerované číslo větší než požadované maximum, generování čísla se opakuje, jinak se funkce úspěšně ukončí.


; -----------------------------------------------------------------------------
;                 Get random signed byte with max value
; -----------------------------------------------------------------------------
; INPUT:	AL = maximal signed value (-128 to +127)
; OUTPUT:	AL = random byte (0 to maximal value)
; -----------------------------------------------------------------------------

RandMaxChar:	or	al,al		; is it unsigned value?
		jns	RandMaxByte	; it is unsigned value or zero
		neg	al		; correct maximal value
		call	RandMaxByte	; generate random byte
		neg	al		; correct result
		ret

Funkce RandMaxChar vygeneruje náhodné číslo "bajt se znaménkem" s rozsahem 0 až dané maximum MAX (včetně nuly a maximální hodnoty), které funkci předáme v registru AL. Maximum může mít rozsah hodnot -128 až +127. Výsledné číslo obdržíme v registru AL.

K vygenerování čísla se znaménkem využijeme funkci generování čísla bez znaménka. Je-li požadované maximum nezáporné (tj. kladná čísla a nula), použije se přímo funkce RandMaxByte. Je-li požadované maximum záporné, převede se nejdříve negací na kladné číslo, vygeneruje se náhodné kladné číslo pomocí RandMaxByte a výsledek se opět negací převede na záporné číslo.


; -----------------------------------------------------------------------------
;               Get random unsigned word with max value
; -----------------------------------------------------------------------------
; INPUT:	AX = maximal unsigned value (0 to 65535)
; OUTPUT:	AX = random word (0 to maximal value)
; -----------------------------------------------------------------------------

; ------------- Check maximal value 0

RandMaxWord:	or	ax,ax		; zero maximal value?
		jz	RandMaxWord8	; maximal value is 0

; ------------- Push registers

		push	ecx		; push ECX
		push	edx		; push EDX

; ------------- Prepare mask of maximal value

		bsr	cx,ax		; ECX <- bits of maximal value
		xor	edx,edx		; EDX <- 0
		mov	dl,B1		; EDX <- B1, maximal value bit
		shl	edx,cl		; EDX <- maximal value
		dec	edx		; DX <- mask of result
		xchg	ax,cx		; CX <- maximal value

; ------------- Find required value

RandMaxWord2:	call	RandWord	; get random word (-> AX)
		and	ax,dx		; mask result
		cmp	ax,cx		; is this value allowed?
		ja	RandMaxWord2	; invalid value, next attempt

; ------------- Pop registers

		pop	edx		; pop EDX
		pop	ecx		; pop ECX
RandMaxWord8:	ret

Funkce RandMaxWord vygeneruje náhodné číslo "slovo bez znaménka" s rozsahem 0 až dané maximum MAX (včetně nuly a maximální hodnoty), které funkci předáme v registru AX. Maximum může mít rozsah hodnot 0 až 65535. Výsledné číslo obdržíme v registru AX.

Na začátku funkce nejdříve otestujeme případ, kdy je požadována maximální hodnota 0. V tom případě se funkce ukončí okamžitě, protože zadané maximum 0 je současně jediným možným generovaným číslem.

Funkce používá jako pracovní registry ECX a EDX, jejich obsahy jsou proto uchovány pomocí instrukcí PUSH/POP.

Před samotným generováním čísla se připraví maska maximální hodnoty. Instrukcí BSR se vyhledá pozice nejvyššího bitu "1" v registru AX. Nalezená bitová pozice se uloží do registru CX.

Do registru EDX se připraví hodnota 2 (tj. bit 1) a rotací doleva o zjištěný počet bitů a dekrementací výsledku se vygeneruje maska maximální hodnoty čísla. Například pro maximální hodnotu 3E7h (=999) bude pozice nejvyššího bitu 9, maximální hodnota pro masku bude 2<<9 = 2*200h = 400h (=1024) a maska náhodného čísla bude 3FFh (=1023).

Maximální požadovaná hodnota čísla se uchová do registru CX pro pozdější použití.

Nyní se již může přistoupit ke generování náhodného čísla. Pomocí funkce RandWord se vygeneruje do registru AX náhodné číslo s rozsahem 0 až 65535. Číslo se zamaskuje maskou z registru DX a výsledek se porovná s požadovaným maximem v registru CX. Je-li vygenerované číslo větší než požadované maximum, generování čísla se opakuje, jinak se funkce úspěšně ukončí.


; -----------------------------------------------------------------------------
;                Get random signed word with max value
; -----------------------------------------------------------------------------
; INPUT:	AX = maximal signed value (-32768 to +32767)
; OUTPUT:	AX = random word (0 to maximal value)
; -----------------------------------------------------------------------------

RandMaxShort:	or	ax,ax		; is it unsigned value?
		jns	RandMaxWord	; it is unsigned value or zero
		neg	ax		; correct maximal value
		call	RandMaxWord	; generate random word
		neg	ax		; correct result
		ret

Funkce RandMaxShort vygeneruje náhodné číslo "slovo se znaménkem" s rozsahem 0 až dané maximum MAX (včetně nuly a maximální hodnoty), které funkci předáme v registru AX. Maximum může mít rozsah hodnot -32768 až +32767. Výsledné číslo obdržíme v registru AX.

K vygenerování čísla se znaménkem využijeme funkci generování čísla bez znaménka. Je-li požadované maximum nezáporné (tj. kladná čísla a nula), použije se přímo funkce RandMaxWord. Je-li požadované maximum záporné, převede se nejdříve negací na kladné číslo, vygeneruje se náhodné kladné číslo pomocí RandMaxWord a výsledek se opět negací převede na záporné číslo.


; -----------------------------------------------------------------------------
;             Get random unsigned double word with max value
; -----------------------------------------------------------------------------
; INPUT:	EAX = maximal unsigned value (0 to 0ffffffffh)
; OUTPUT:	EAX = random double word (0 to maximal value)
; -----------------------------------------------------------------------------

; ------------- Check maximal value 0

RandMaxDWord:	or	eax,eax		; zero maximal value?
		jz	RandMaxDWord8	; maximal value is 0

; ------------- Push registers

		push	ecx		; push ECX
		push	edx		; push EDX

; ------------- Prepare mask of maximal value

		bsr	ecx,eax		; ECX <- bits of maximal value
		xor	edx,edx		; EDX <- 0
		mov	dl,B1		; EDX <- B1, maximal value bit
		shl	edx,cl		; EDX <- maximal value
		dec	edx		; EDX <- mask of result
		xchg	eax,ecx		; ECX <- maximal value

; ------------- Find required value

RandMaxDWord4:	call	RandDWord	; get random double word (-> EAX)
		and	eax,edx		; mask result
		cmp	eax,ecx		; is this value allowed?
		ja	RandMaxDWord4	; invalid value, next attempt

; ------------- Pop registers

		pop	edx		; pop EDX
		pop	ecx		; pop ECX
RandMaxDWord8:	ret

Funkce RandMaxDWord vygeneruje náhodné číslo "dvojslovo bez znaménka" s rozsahem 0 až dané maximum MAX (včetně nuly a maximální hodnoty), které funkci předáme v registru EAX. Maximum může mít rozsah hodnot 0 až 0ffffffffh. Výsledné číslo obdržíme v registru EAX.

Na začátku funkce nejdříve otestujeme případ, kdy je požadována maximální hodnota 0. V tom případě se funkce ukončí okamžitě, protože zadané maximum 0 je současně jediným možným generovaným číslem.

Funkce používá jako pracovní registry ECX a EDX, jejich obsahy jsou proto uchovány pomocí instrukcí PUSH/POP.

Před samotným generováním čísla se připraví maska maximální hodnoty. Instrukcí BSR se vyhledá pozice nejvyššího bitu "1" v registru EAX. Nalezená bitová pozice se uloží do registru ECX.

Do registru EDX se připraví hodnota 2 (tj. bit 1) a rotací doleva o zjištěný počet bitů a dekrementací výsledku se vygeneruje maska maximální hodnoty čísla. Například pro maximální hodnotu 0F423Fh (=999999) bude pozice nejvyššího bitu 19, maximální hodnota pro masku bude 2<<19 = 2*80000h = 100000h (=1048576) a maska náhodného čísla bude 0FFFFFh (=1048575).

Maximální požadovaná hodnota čísla se uchová do registru ECX pro pozdější použití.

Nyní se již může přistoupit ke generování náhodného čísla. Pomocí funkce RandDWord se vygeneruje do registru EAX náhodné číslo s rozsahem 0 až 0ffffffffh, číslo se zamaskuje maskou z registru EDX a výsledek se porovná s požadovaným maximem v registru ECX. Je-li vygenerované číslo větší než požadované maximum, generování čísla se opakuje, jinak se funkce úspěšně ukončí.


; -----------------------------------------------------------------------------
;             Get random signed double word with max value
; -----------------------------------------------------------------------------
; INPUT:	EAX = maximal signed value (-80000000h to +7fffffffh)
; OUTPUT:	EAX = random double word (0 to maximal value)
; -----------------------------------------------------------------------------

RandMaxLong:	or	eax,eax		; is it unsigned value?
		jns	RandMaxDWord	; it is unsigned value or zero
		neg	eax		; correct maximal value
		call	RandMaxDWord	; generate random double word
		neg	eax		; correct result
		ret

Funkce RandMaxLong vygeneruje náhodné číslo "dvojslovo se znaménkem" s rozsahem 0 až dané maximum MAX (včetně nuly a maximální hodnoty), které funkci předáme v registru EAX. Maximum může mít rozsah hodnot -80000000h až +7fffffffh. Výsledné číslo obdržíme v registru EAX.

K vygenerování čísla se znaménkem využijeme funkci generování čísla bez znaménka. Je-li požadované maximum nezáporné (tj. kladná čísla a nula), použije se přímo funkce RandMaxDWord. Je-li požadované maximum záporné, převede se nejdříve negací na kladné číslo, vygeneruje se náhodné kladné číslo pomocí RandMaxDWord a výsledek se opět negací převede na záporné číslo.


; -----------------------------------------------------------------------------
;            Get random unsigned quadruple word with max value
; -----------------------------------------------------------------------------
; INPUT:	EDX:EAX = maximal unsigned value (0 to 0ffffffffffffffffh)
; OUTPUT:	EDX:EAX = random quadruple word (0 to maximal value)
; -----------------------------------------------------------------------------

; ------------- Check maximal value 0

RandMaxQWord:	or	edx,edx		; is maximal value QWORD?	
		jz	RandMaxDWord	; only DWORD required

; ------------- Push registers

		push	ebx		; push EBX
		push	ecx		; push ECX
		push	esi		; push ESI

; ------------- Prepare mask of maximal value

		bsr	ecx,edx		; ECX <- bits of maximal value HIGH
		xor	ebx,ebx		; EBX <- 0
		mov	bl,B1		; EBX <- B1, maximal value bit
		shl	ebx,cl		; EBX <- maximal value HIGH
		dec	ebx		; EBX <- mask of result HIGH
		xchg	eax,ecx		; ECX <- maximal value LOW
		mov	esi,edx		; ESI <- maximal value HIGH

; ------------- Find required value

RandMaxQWord6:	call	RandQWord	; get random quadruple word (->EDX:EAX)
		and	edx,ebx		; mask result HIGH
		cmp	edx,esi		; check value HIGH
		jb	RandMaxQWord8	; value is OK
		ja	RandMaxQWord6	; invalid value, next attempt		
		cmp	eax,ecx		; check value LOW
		ja	RandMaxQWord6	; invalid value, next attempt

; ------------- Pop registers

RandMaxQWord8:	pop	esi		; pop ESI
		pop	ecx		; pop ECX
		pop	ebx		; pop EBX
		ret

Funkce RandMaxQWord vygeneruje náhodné číslo "čtyřslovo bez znaménka" s rozsahem 0 až dané maximum MAX (včetně nuly a maximální hodnoty), které funkci předáme v registrech EDX:EAX. Maximum může mít rozsah hodnot 0 až 0ffffffffffffffffh. Výsledné číslo obdržíme v registrech EDX:EAX.

Na začátku funkce nejdříve otestujeme případ, kdy je požadována maximální hodnota spadající do rozsahu dvojslova. Pokud je vyšší dvojslovo maxima rovno nule, předá se řízení funkci pro generování dvojslova v daném rozsahu (registr EDX si přitom uchová nulovou hodnotu).

Funkce používá jako pracovní registry EBX, ECX a ESI, jejich obsahy jsou proto uchovány pomocí instrukcí PUSH/POP.

Před samotným generováním čísla se připraví maska maximální hodnoty. Instrukcí BSR se vyhledá pozice nejvyššího bitu "1" v registru EDX, tedy ve vyšším dvojslovu maxima. Víme už z testu na začátku funkce, že obsah EDX je nenulový, proto nás nezajímá registr EAX s nižší hodnotou maxima. Nalezená bitová pozice se uloží do registru ECX.

Do registru EBX se připraví hodnota 2 (tj. bit 1) a rotací doleva o zjištěný počet bitů a dekrementací výsledku se vygeneruje maska maximální hodnoty čísla. Je to maska pouze pro vyšší dvojslovo generovaného čísla, protože víme, že generujeme rozsah čísel větší než je rozsah jednoho dvojslova.

Maximální požadovaná hodnota čísla se uchová do registrů ESI:ECX pro pozdější použití.

Nyní se již může přistoupit ke generování náhodného čísla. Pomocí funkce RandQWord se vygeneruje do registrů EDX:EAX náhodné číslo s rozsahem 0 až 0ffffffffffffffffh. Vyšší dvojslovo čísla se zamaskuje maskou z registru EBX a výsledek se porovná s požadovaným maximem v registrech ESI:ECX. Jedná se o porovnání dvou dvojslov a proto ho musíme provést ve dvou krocích - nejdříve porovnání vyššího dvojslova a pokud si jsou čísla rovna tak porovnání nižšího dvojslova. Je-li vygenerované číslo větší než požadované maximum, generování čísla se opakuje, jinak se funkce úspěšně ukončí.


; -----------------------------------------------------------------------------
;            Get random signed quadruple word with max value
; -----------------------------------------------------------------------------
; INPUT:	EDX:EAX = maximal signed value (-8000000000000000h 
;					         to +7fffffffffffffffh)
; OUTPUT:	EDX:EAX = random quadruple word (0 to maximal value)
; -----------------------------------------------------------------------------

RandMaxInt64:	or	edx,edx		; is it unsigned value?
		jns	RandMaxQWord	; it is unsigned value or zero
		neg	eax		; correct maximal value LOW
		adc	edx,byte 0	; carry
		neg	edx		; correct maximal value HIGH
		call	RandMaxQWord	; generate random double word
		neg	eax		; correct result LOW
		adc	edx,byte 0	; carry
		neg	edx		; correct result HIGH
		ret

Funkce RandMaxInt64 vygeneruje náhodné číslo "čtyřslovo se znaménkem" s rozsahem 0 až dané maximum MAX (včetně nuly a maximální hodnoty), které funkci předáme v registrech EDX:EAX. Maximum může mít rozsah hodnot -8000000000000000h až +7fffffffffffffffh. Výsledné číslo obdržíme v registrech EDX:EAX.

K vygenerování čísla se znaménkem využijeme funkci generování čísla bez znaménka. Je-li požadované maximum nezáporné (tj. kladná čísla a nula), použije se přímo funkce RandMaxQWord. Je-li požadované maximum záporné, převede se nejdříve negací na kladné číslo, vygeneruje se náhodné kladné číslo pomocí RandMaxQWord a výsledek se opět negací převede na záporné číslo.

Negaci čísla uloženého ve 2 registrech musíme provést ve více krocích. Negací registru EAX provedeme změnu znaménka nižšího dvojslova čísla. Pokud byl obsah registru EAX nulový, vynuluje se příznak CF. Byl-li obsah registru nenulový, příznak CF se nastaví. V případě nenulového registru se inkrementuje vyšší dvojslovo čísla (obsah registru EDX se inkrementuje přičtením 0 spolu s příznakem přenosu). Nakonec se provede negace vyššího dvojslova.

Vysvětlení pro uvedený postup výpočtu negace je následující: Instrukci negace si můžeme představit jako operaci "0 - číslo", tedy odečtení čísla od nuly. Je zřejmé, že k nastavení příznaku přenosu dojde vždy, když je číslo nenulové. Když bychom dělali negaci pomocí instrukcí pro odečítání, odečetli bychom nižší dvojslovo od nuly instrukcí SUB a vyšší dvojslovo instrukcí SBB, tedy odečetli bychom vyšší dvojslovo i příznak CF. To je stejná operace, jako když odečteme druhý operand s přičteným příznakem CF, příznak CF tedy můžeme přičíst k druhému operandu již před provedením operace.


; -----------------------------------------------------------------------------
;                 Get random float with max value (10 digits)
; -----------------------------------------------------------------------------
; INPUT:	ST0 = maximal value (can be signed)
; OUTPUT:	ST0 = random float (0 to max value)
; NOTES:	Warning - this function uses FPU (push/pop state if in kernel).
; -----------------------------------------------------------------------------

RandMaxFloat:	call	RandFloatFPU	; generate random float
		fmulp	st1,st0		; multiple with max. value
		ret	

Funkce RandMaxFloat vygeneruje náhodné číslo s daným maximem s přesností 10 číslic. Maximum je dáno na vstupu funkce v registru ST0 koprocesoru a může mít jak kladnou tak i zápornou hodnotu. Výsledek je navrácen opět v registru ST0. Protože funkce používá matematický koprocesor, je nutné pamatovat na uchování stavu koprocesoru v případě, že funkce je interně používána jádrem systému.


; -----------------------------------------------------------------------------
;          Get random double float with max value (16 digits)
; -----------------------------------------------------------------------------
; INPUT:	ST0 = maximal value (can be signed)
; OUTPUT:	ST0 = random double float (0 to max value)
; NOTES:		Warning - this function uses FPU (push/pop state if in kernel).
; -----------------------------------------------------------------------------

RandMaxDouble:	call	RandDoubleFPU	; generate random double float
		fmulp	st1,st0		; multiple with max. value
		ret	

Funkce RandMaxDouble vygeneruje náhodné číslo s daným maximem s přesností 16 číslic. Maximum je dáno na vstupu funkce v registru ST0 koprocesoru a může mít jak kladnou tak i zápornou hodnotu. Výsledek je navrácen opět v registru ST0. Protože funkce používá matematický koprocesor, je nutné pamatovat na uchování stavu koprocesoru v případě, že funkce je interně používána jádrem systému.


Obsah / Utility / RANDOM / Náhodné číslo s danou maximální hodnotou