8086随机数生成器(不仅限于使用系统时间)?

5

我正在使用汇编8086emu,需要一个可以生成8个数字的数字生成器。
我尝试使用@johnfound的以下代码:

RANDGEN:         ; generate a rand no using the system time

RANDSTART:
   MOV AH, 00h  ; interrupts to get system time        
   INT 1AH      ; CX:DX now hold number of clock ticks since midnight      

   mov  ax, dx
   xor  dx, dx
   mov  cx, 10    
   div  cx       ; here dx contains the remainder of the division - from 0 to 9

   add  dl, '0'  ; to ascii from '0' to '9'
   mov ah, 2h   ; call interrupt to display a value in DL
   int 21h    
RET    

但是只有在生成一个数字时才有用。由于时钟每秒只跳动18.2次,因此重复调用会得到相同的数字。

我尝试创建伪随机函数,但我对汇编语言还不太熟悉,没有成功。我想知道是否有一种方法可以在emu8086中实现类似于Java的Math.random()函数。


1
尝试实现一个 xorshift 随机数生成器。这应该相当简单且实用。 - fuz
此外,汇编中的随机数有一个详细的答案,使用不同的LCG,也许比这个更好的参数。 - Peter Cordes
请参阅 https://www.felixcloutier.com/x86/rdrand,了解现代x86 CPU中“rdrand ax”是有效指令。除了答案和链接的重复提到的经典LCG之外,还有像xorshift+ https://en.wikipedia.org/wiki/Xorshift 或 https://en.wikipedia.org/wiki/Linear-feedback_shift_register 这样的东西。使用uint16_t的任一缩减实现都可以合理地完成。对于实际的8086,请记住var << 15可以通过ror ax, 1 / and ax, 8000h来完成,这可能比没有移位器更快。 - Peter Cordes
使用时钟中断生成随机数(Generating random numbers using the interrupt of clock)展示了使用32位寄存器的xorshift算法,因此它在386及更高版本的16位模式下可用,不适用于emu8086。 - Peter Cordes
2个回答

7

一个简单的伪随机数生成器将当前数字乘以25173,然后加上13849。这个值现在成为新的随机数。
如果您像之前一样从系统计时器开始(这称为种子随机数生成器),那么这一系列数字对于简单任务来说将足够随机!

MOV     AH, 00h   ; interrupt to get system timer in CX:DX 
INT     1AH
mov     [PRN], dx
call    CalcNew   ; -> AX is a random number
xor     dx, dx
mov     cx, 10    
div     cx        ; here dx contains the remainder - from 0 to 9
add     dl, '0'   ; to ascii from '0' to '9'
mov     ah, 02h   ; call interrupt to display a value in DL
int     21h    
call    CalcNew   ; -> AX is another random number
...
ret

; ----------------
; inputs: none  (modifies PRN seed variable)
; clobbers: DX.  returns: AX = next random number
CalcNew:
    mov     ax, 25173          ; LCG Multiplier
    mul     word ptr [PRN]     ; DX:AX = LCG multiplier * seed
    add     ax, 13849          ; Add LCG increment value
    ; Modulo 65536, AX = (multiplier*seed+increment) mod 65536
    mov     [PRN], ax          ; Update seed = return value
    ret

这实现了一个带有2的幂模数的线性同余生成器(LCG)%65536 是免费的,因为乘积和增量的低16位在AX中,而高位不是。

谢谢!我研究了这个主题并制作了一个线性同余生成器。 - Koren Minchev
@MichaelPetch:是的,在那个时候,DX 已经没有用了。我们只需要一个 16x16 => 16 位乘法。因为在 AX 的情况下,DX 的值是唯一确定的,所以 DX 中没有额外的熵。也就是说,如果你能找到产生这个 AX 值的 16 位 PRN 种子,你也可以计算出 DX。因此,在 DX:AX 中将其称为 32 位返回值是没有意义的。 - Peter Cordes

2

好的想法,但实现有缺陷。上面展示的代码生成了一个可预测的偶-奇-偶-奇-偶-奇等等的模式。这不太“随机”。维基百科警告说,线性同余生成器在高位上很随机,但低位上则不是。解决方法是在保存种子后插入SHR AX,5。种子仍会翻转成偶-奇-偶-奇的形式,但从种子获得的随机数会忽略最低的五个有效位。CalcNew函数的结尾应该如下所示:

    mov     [PRN], ax          ; Update seed
    shr     ax,5               ; Discard 5 bits
    ret

这里的“五位”不是必须的,只是因为少于五位的随机性不够强,而多于五位会降低您选择随机数大小的能力。AX仅有16位;舍弃其中五位后,您将得到一个介于0和2047之间的随机数,这非常适合下一步除以某个数字n并取余数。当n为10时,2047就足够了。但根据应用程序的不同,n可能会更大。如果您需要一个随机的ASCII字符,则将除以96。如果您舍弃了七位而不是五位,则将得到一个从0到511的随机AX,您将除以96来获得余数。这会使结果偏向于较低的余数发生频率高于较高的余数。因此,当使用16位CPU时,五位是一个很好的折衷方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接