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

Obsah / Utility / RANDOM / Náhodné číslo v plném rozsahu

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


Náhodné číslo v plném rozsahu


; -----------------------------------------------------------------------------
;                            Get random byte
; -----------------------------------------------------------------------------
; OUTPUT:	AL = random byte (0 to 255)
; -----------------------------------------------------------------------------

RandByte:	push	edx		; push EDX
		push	eax		; push EAX
		RANDNEXT		; next global random seed
		shr	eax,16		; EAX <- random word
		xchg	eax,edx		; EDX <- push random word
		pop	eax		; pop EAX
		mov	al,dl		; AL <- random byte
		pop	edx		; pop EDX
		ret

Funkce RandByte poskytne v registru AL náhodné číslo v rozsahu 0 až 255. Nejdříve je pomocí makra RANDNEXT vypočtena nová hodnota generátoru náhody. Z výsledku EAX:EDX je převzat bajt od 16. bitu registru EAX, který má rovnoměrnější rozdělení náhodnosti než nižší bajty. Výsledek je uložen do registru AL, ostatní registry (včetně zbytku registru EAX) nejsou ovlivněny.


; -----------------------------------------------------------------------------
;                            Get random word
; -----------------------------------------------------------------------------
; OUTPUT:	AX = random word (0 to 65535)
; -----------------------------------------------------------------------------

RandWord:	push	edx		; push EDX
		push	eax		; push EAX
		RANDNEXT		; next global random seed
		shr	eax,16		; EAX <- random word
		xchg	eax,edx		; EDX <- push random word
		pop	eax		; pop EAX
		xchg	ax,dx		; AX <- random word
		pop	edx		; pop EDX
		ret

Funkce RandWord poskytne v registru AX náhodné číslo v rozsahu 0 až 65535. Nejdříve je pomocí makra RANDNEXT vypočtena nová hodnota generátoru náhody. Z výsledku EAX:EDX je převzato slovo od 16. bitu registru EAX, které má rovnoměrnější rozdělení náhodnosti než nižší slovo. Výsledek je uložen do registru AX, ostatní registry (včetně zbytku registru EAX) nejsou ovlivněny.


; -----------------------------------------------------------------------------
;                       Get random quadruple word
; -----------------------------------------------------------------------------
; OUTPUT:	EDX:EAX = random quadruple word (0 to 0ffffffffffffffffh)
; NOTES:	RANDNEXT result is not used to avoid number predictions.
; -----------------------------------------------------------------------------

RandQWord:	push	edx		; push EDX
		RANDNEXT		; next global random seed
		pop	edx		; pop EDX
		xchg	eax,edx		; EDX <- first random double word

; RandDWord must follow!

; -----------------------------------------------------------------------------
;                       Get random double word
; -----------------------------------------------------------------------------
; OUTPUT:	EAX = random double word (0 to 0ffffffffh)
; -----------------------------------------------------------------------------

RandDWord:	push	edx		; push EDX
		RANDNEXT		; next global random seed
		pop	edx		; pop EDX
		ret

Funkce RandDWord poskytne v registru EAX náhodné číslo v rozsahu 0 až 0ffffffffh. Nejdříve je pomocí makra RANDNEXT vypočtena nová hodnota generátoru náhody. Z výsledku EAX:EDX je převzata pouze hodnota registru EAX. Hodnota registru EDX je uchována instrukcemi PUSH/POP.

Funkce RandQWord poskytne v dvojici registrů EDX:EAX náhodné číslo v rozsahu 0 až 0ffffffffffffffffh. Nejdříve vypočte pomocí makra RANDNEXT první dvojslovo, výsledek uloží do registru EDX a poté pokračuje funkcí RandDWord, která vypočte náhodné číslo pro registr EAX.

Pro výpočet RandQWord není použit celý výsledek výpočtu generátoru náhody EAX:EDX z důvodu, aby se uživateli nezpřístupnila celá hodnota generátoru náhody a nebylo tak možné dopředu předpovídat náhodná čísla.


; -----------------------------------------------------------------------------
;                  Get random float, precision 7 decimal digits
; -----------------------------------------------------------------------------
; OUTPUT:	EAX = random float (0 to 1, including "0", excluding "1")
; NOTES:	Number in EAX is single-precision floating-point IEE 754.
;		This function does not use FPU.
;		To load result into FPU, you can use this sequence:
;			push	eax		; push result into stack
;			fld	dword [esp]	; load result into FPU
;			pop	eax		; pop result from the stack
;		Possible values (only 7 decimal digits are significant):
;			0.0000000
;			0.0000001
;			0.0000002
;			...
;			0.4999999
;			0.5000000
;			0.5000001
;			...
;			0.9999999
; -----------------------------------------------------------------------------

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

RandFloat:	push	ecx		; push ECX

; ------------- Generate random double word (-> EAX)

		push	edx		; push EDX
		RANDNEXT		; next global random seed (-> EAX)
		pop	edx		; pop EDX
		and	eax,~1ffh	; clear unused bits

; ------------- Find highest bit "1" (-> ECX)

		bsr	ecx,eax		; ECX <- find highest bit "1"
		jz	RandFloat2	; zero number

; ------------- Prepare mantissa (-> EAX)

		ror	eax,cl		; EAX <- rotate highest bit "1" out
		shr	eax,9		; EAX <- shift mantissa to its position

; ------------- Prepare exponent (-> EDX)

		mov	ch,127-32	; CH <- exponent for "1.0000" - 32
		add	ch,cl		; CH <- exponent
		movzx	ecx,ch		; ECX <- exponent
		shl	ecx,23		; ECX <- rotate exponent to position

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

		or	eax,ecx		; EAX <- add exponent to result
RandFloat2:	pop	ecx		; pop ECX
		ret

Funkce RandFloat vypočte náhodné desetinné číslo v rozsahu 0 (včetně) až 1 (vyjma) s přesností 7 desetinných míst, výsledek navrátí v registru EAX ve formátu IEE 754 single-precision. Tato funkce nepoužívá matematický koprocesor (FPU) a proto může být volána z jádra systému bez nutnosti uchovávat obsah koprocesoru. K načtení výsledku do registru koprocesoru lze použít posloupnost instrukcí: "push eax", "fld dword ptr [esp]", "pop eax".

Funkce vygeneruje náhodné číslo v rozsahu dvojslovo a vynuluje nejnižších 9 bitů čísla, protože využívá pouze 23 bitů čísla. Bez vynulování těchto nejnižších 9 bitů by se zvýšila četnost velmi malých čísel z důvodu zaokrouhlení nahoru.

Instrukce BSR vyhledá nejvyšší bit "1" v čísle a jeho bitovou pozici uloží do registru ECX. Je-li náhodné číslo rovno nule, funkce se předčasně ukončí s navrácením nulové hodnoty.

Formát IEE 754 single-precision obsahuje v nižších 23 bitech normalizovanou mantisu čísla, přičemž nejvyšší bit "1" mantisy je vypuštěn. Funkce připraví mantisu čísla tak, že nejdříve rotuje náhodné číslo v registru EAX doprava tak, aby se nejvyšší bit "1" dostal na bitovou pozici 0. K tomu využije registr CL obsahující index pozice bitu v číslu. Po provedení rotace posune číslo o 9 bitů doprava, tím posune mantisu na správnou bitovou pozici, tj. bity 0 až 22.

Exponent formátu IEE 754 zabírá 8 bitů od bitu 23 po bit 30 s offsetem 127. Hodnota exponentu se vypočte jako "127 - 32 + počet provedených rotací mantisy doleva". Tím se číslo převede do rozsahu 0 až 1. Vypočtený exponent se posune na správnou bitovou pozici rotací exponentu o 23 bitů doleva.

Na závěr se sestaví výsledná hodnota čísla sloučením mantisy s exponentem. Vygenerované číslo má hodnoty z řady 0.0000000, 0.0000001, 0.0000002, ... 0.4999999, 0.5000000, 0.5000001, ... 0.9999999.


; -----------------------------------------------------------------------------
;           Get random number using FPU, precision 10 decimal digits
; -----------------------------------------------------------------------------
; OUTPUT:	ST0 = random float (0 to 1, including "0", excluding "1")
; NOTES:	Warning - this function uses FPU (push/pop state if in kernel).
;		Possible values (only 10 decimal digits are significant):
;			0.0000000000
;			0.0000000002
;			0.0000000005
;			...
;			0.4999999998
;			0.5000000000
;			0.5000000002
;			...
;			0.9999999998
; -----------------------------------------------------------------------------

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

RandFloatFPU:	push	eax		; push EAX

; ------------- Prepare HIGH dword of the number (= 0)

		xor	eax,eax		; EAX <- 0
		push	eax		; store HIGH dword of the number

; ------------- Generate LOW dword of the number

		push	edx		; push EDX
		RANDNEXT		; next global random seed (-> EAX)
		pop	edx		; pop EDX
		push	eax		; store number to the stack

; ------------- Recalc number to range 0 to 1

		fwait			; synchronize FPU
		fild	qword [esp]	; ST0 <- random number
		fmul	qword [RandFltCoef] ; recalc to range 0 to 1
		pop	eax		; destroy number from the stack
		pop	eax		; destroy HIGH dword from the stack

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

		pop	eax		; pop EAX
		ret	

; ------------- Float coefficient 1/100000000h (= 3df0000000000000h)

RandFltCoef:	dq	0.00000000023283064365386962890625

Funkce RandFloatFPU vypočte náhodné desetinné číslo v rozsahu 0 (včetně) až 1 (vyjma) s přesností 10 desetinných míst, výsledek navrátí v registru ST0 matematického koprocesoru (FPU). 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.

Funkce vygeneruje náhodné číslo v rozsahu dvojslovo a načte ho do registru ST0 koprocesoru. Instrukce FILD čte celé číslo se znaménkem, proto je náhodné číslo rozšířeno na QWORD doplněním vyššího dvojslova s hodnotou nula.

Vygenerované náhodné číslo je dále přepočítáno do rozsahu 0 až 1 tak, že je vyděleno hodnotou 100000000h, což je maximální rozsah generovaného náhodného čísla. Pro zvýšení efektivnosti je namísto dělení použito násobení převrácenou hodnotou čísla - konstanta RandFltCoef.

Vygenerované číslo má hodnoty z řady 0.0000000000, 0.0000000002, 0.0000000005, ... 0.4999999998, 0.5000000000, 0.5000000002, ... 0.9999999998.


; -----------------------------------------------------------------------------
;                Get random double, precision 16 decimal digits
; -----------------------------------------------------------------------------
; OUTPUT:	EDX:EAX = random double (0 to 1, including "0", excluding "1")
; NOTES:	Number in EDX:EAX is double-precision floating-point IEE 754.
;		This function does not use FPU.
;		To load result into FPU, you can use this sequence:
;			push	edx		; push result HIGH into stack
;			push	eax		; push result LOW into stack
;			fld	qword [esp]	; load result into FPU
;			pop	eax		; pop result LOW from the stack
;			pop	edx		; pop result HIGH from stack
;		Possible values (only 16 decimal digits are significant):
;			0.0000000000000000
;			0.0000000000000001
;			0.0000000000000002
;			...
;			0.4999999999999999
;			0.5000000000000000
;			0.5000000000000001
;			...
;			0.9999999999999999
; -----------------------------------------------------------------------------

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

RandDouble:	push	ebx		; push EBX
		push	ecx		; push ECX
		xor	ecx,ecx		; ECX <- 0

; ------------- Generate random quadruple word (-> EDX:EAX)

		call	RandQWord	; generate random quadruple word
		and	edx,1fffffh	; mask mantissa HIGH (+ high "1" bit)
		jz	RandDouble4	; number is only DWORD

; ------------- Find highest bit "1" (-> EBX)

		bsr	ebx,edx		; EBX <- find highest bit "1"

; ------------- Prepare mantissa (-> EDX:EAX)

		mov	cl,20		; ECX <- maximal possible bit position
		sub	cl,bl		; ECX <- number of bits to rotate
		shld	edx,eax,cl	; EDX <- shift mantissa HIGH
		shl	eax,cl		; EAX <- shift mantissa LOW

; ------------- Prepare exponent (-> EDX)

		mov	bx,1022		; EBX <- exponent for "1.0000" - 1
		jmp	short RandDouble6

; ------------- Find highest bit "1" (-> EBX)

RandDouble4:	bsr	ebx,eax		; EBX <- find highest bit "1"
		jz	RandDouble8	; zero number

; ------------- Prepare mantissa (-> EDX:EAX)

		mov	cl,31		; ECX <- maximal possible bit position
		sub	cl,bl		; ECX <- number of bits to rotate
		shl	eax,cl		; EAX <- shift mantissa
		shld	edx,eax,21	; EDX <- mantissa HIGH
		shl	eax,21		; EAX <- mantissa LOW
		
; ------------- Prepare exponent (-> EDX)

		mov	bx,1022-21	; EBX <- exponent for "1.0000" - 1 - 21

; ------------- Composite result

RandDouble6:	sub	ebx,ecx		; EBX <- new exponent
		shl	ebx,52-32	; EBX <- rotate exponent to position
		and	edx,0fffffh	; mask mantissa HIGH
		or	edx,ebx		; add exponent to result

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

RandDouble8:	pop	ecx		; pop ECX
		pop	ebx		; pop EBX
		ret

Funkce RandDouble vypočte náhodné desetinné číslo v rozsahu 0 (včetně) až 1 (vyjma) s přesností 16 desetinných míst, výsledek navrátí v registrech EDX:EAX ve formátu IEE 754 double-precision. Tato funkce nepoužívá matematický koprocesor (FPU) a proto může být volána z jádra systému bez nutnosti uchovávat obsah koprocesoru. K načtení výsledku do registru koprocesoru lze použít posloupnost instrukcí: "push edx", "push eax", "fld qword ptr [esp]", "pop eax", "pop edx".

Funkce vygeneruje náhodné číslo v rozsahu čtyřslovo. Vynuluje nejvyšších 11 bitů čísla, protože využívá pouze 53 bitů čísla. Je-li vyšší dvojslovo čísla (tj. registr EDX) nula, pokračuje se částí pro náhodné číslo v rozsahu DWORD.

Má-li číslo rozsah čtyřslova, vyhledá se ve vyšším dvojslovu (registr EDX) pomocí instrukce BSR pozice nejvyššího bitu "1" a uloží se do registru EBX.

Připraví se mantisa čísla - vypočte se počet posuvů, které je potřeba udělat, aby se nejvyšší bit čísla dostal na bitovou pozici 20 (tj. posun = 20 - pozice nejvyššího bitu) a provede se posun mantisy doleva o zjištěný počet posuvů. Posuvy se dělají přes nižší i vyšší dvojslovo čísla, proto se využívá instrukce SHLD, která zajistí vícebitový přenos mezi registry. Mantisa bude na bitové pozici 0 až 52 čtyřslova EDX:EAX, nejvyšší bit 52 se později odstraní (je automaticky daný formátem čísla).

Do registru EBX se připraví výchozí střední hodnota exponentu, od které se později odečte bitový posun mantisy.

Druhá větev funkce pokračuje v případě, že vygenerované náhodné číslo má rozsah pouze dvojslovo (vyšší dvojslovo je 0). Pomocí instrukce BSR se vyhledá pozice nejvyššího bitu "1" v nižším dvojslově čísla (tj. v registru EAX) a uloží se do registru EBX. V případě nulové hodnoty náhodného čísla se funkce ukončí ihned, výsledkem generátoru je hodnota 0.

Připraví se mantisa čísla - vypočte se počet posuvů, které je potřeba udělat, aby se nejvyšší bit čísla dostal na bitovou pozici 31 a provede se posun obsahu nižšího dvojslova doleva o vypočtený počet bitů. Poté se mantisa převede na normalizovaný tvar posunem o 21 bitů doleva přes oba registry vygenerovaného čísla. Mantisa bude na bitové pozici 0 až 52 čtyřslova EDX:EAX, nejvyšší bit 52 se později odstraní.

Do registru EBX se připraví výchozí střední hodnota exponentu, od které se později odečte bitový posun mantisy, snížená o počet bitů, o které se mantisa musela posunout v tomto případě, kdy bylo náhodným číslem pouze jedno dvojslovo.

Pokračuje společná část pro obě větve, která zajistí sloučení výsledku. Od výchozí střední hodnoty exponentu v registru EBX je odečten provedený posun mantisy (který je uložen v registru ECX) a exponent je posunut na správnou bitovou pozici 52. Zamaskováním registru EDX se vynuluje nejvyšší bit "1" mantisy, který je automaticky daný formátem čísla a mantisa s exponentem se sloučí.

Vygenerované číslo má hodnoty z řady 0.0000000000000000, 0.0000000000000001, 0.0000000000000002, ... 0.9999999999999999, 0.5000000000000000, 0.5000000000000001, ... 0.9999999999999999.


; -----------------------------------------------------------------------------
;           Get random number using FPU, precision 16 decimal digits
; -----------------------------------------------------------------------------
; OUTPUT:	ST0 = random double (0 to 1)
; NOTES:	Warning - this function uses FPU (push/pop state if in kernel).
;		Possible values (only 16 decimal digits are significant):
;			0.0000000000000000
;			0.0000000000000001
;			0.0000000000000002
;			...
;			0.4999999999999999
;			0.5000000000000000
;			0.5000000000000001
;			...
;			0.9999999999999999
; -----------------------------------------------------------------------------

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

RandDoubleFPU:	push	eax		; push EAX
		push	edx		; push EDX

; ------------- Generate HIGH random number

		RANDNEXT		; next global random seed (-> EAX)
		and	eax,1fffffh	; reset unused bits
		push	eax		; store HIGH number to the stack

; ------------- Generate LOW random number

		RANDNEXT		; next global random seed (-> EAX)
		push	eax		; store LOW number to the stack

; ------------- Recalc number to range 0 to 1

		fwait			; synchronize FPU
		fild	qword [esp]	; ST0 <- random number
		fmul	qword [RandDblCoef] ; recalc to range 0 to 1
		pop	eax		; destroy LOW number from the stack
		pop	eax		; destroy HIGH number from the stack

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

		pop	edx		; pop EDX
		pop	eax		; pop EAX
		ret	

; ------------- Double coefficient 1/20000000000000h (= 3ca0000000000000h)

RandDblCoef:	dq	1.1102230246251565404236316680908e-16

Funkce RandDoubleFPU vypočte náhodné desetinné číslo v rozsahu 0 až 1 s přesností 16 desetinných míst, výsledek navrátí v registru ST0 matematického koprocesoru (FPU). 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.

Funkce vygeneruje náhodné číslo v rozsahu čtyřslovo a načte ho do registru ST0 koprocesoru. Vynuluje nejvyšších 11 bitů čísla, protože nebudou využity.

Vygenerované náhodné číslo je dále přepočítáno do rozsahu 0 až 1 tak, že je vyděleno hodnotou 20000000000000h, což je maximální rozsah generovaného náhodného čísla. Pro zvýšení efektivnosti je namísto dělení použito násobení převrácenou hodnotou čísla - konstanta RandDblCoef.

Vygenerované číslo má hodnoty z řady 0.0000000000000000, 0.0000000000000001, 0.0000000000000002, ... 0.9999999999999999, 0.5000000000000000, 0.5000000000000001, ... 0.9999999999999999.


Obsah / Utility / RANDOM / Náhodné číslo v plném rozsahu