EMU8086中的随机数,不使用DOS/BIOS调用



我是汇编的新手,想知道如何在EMU8086中编写一个程序,该程序在每次运行中都会打印一个不同的随机数。是否可以在不使用中断的情况下执行此操作?

如果您使用的是DOS的真实版本(不是EMU8086)@fuz方法是您可以的方式这样做,并且不需要中断。您只需在BIOS数据区域(BDA)中读取内存地址0x46C(0x00040:0x006C)的32位值的较低16位即可。该位置的值是一个32位值,代表自午夜以来计时器的数量。不幸的是EMU8086不支持此方法。

要在EMU8086中获得一个随机数(系统调用),您可以使用int 1Ah/ah = 0H:

时间 - 获取系统时间

AH = 00h
Return:
CX:DX = number of clock ticks since midnight
AL = midnight flag, nonzero if midnight passed since time last read

您可以使用该值并将其打印出来。该值是半随机的。您可以将其直接打印出来,但最好将其传递到伪随机数发生器(PRNG)作为种子值中。有关基本LCG,请参见下面的部分。打印整数是一个单独的问题,尽管EMU8086具有宏/功能可以做到这一点。此代码可以在1到10之间产生半随机数并打印它:

org 100h
include emu8086.inc                                                       
xor ax,ax            ; xor register to itself same as zeroing register
int 1ah              ; Int 1ah/ah=0 get timer ticks since midnight in CX:DX
mov ax,dx            ; Use lower 16 bits (in DX) for random value
xor dx,dx            ; Compute randval(DX) mod 10 to get num
mov bx,10            ;     between 0 and 9
div bx               ; Divide dx:ax by bx
inc dx               ; DX = modulo from division
                     ;     Add 1 to give us # between 1 and 10 (not 0 to 9)
mov ax,dx            ; Move to AX to print     
call PRINT_NUM_UNS   ; Print value in AX as unsigned
ret
                                                      
DEFINE_PRINT_NUM_UNS ; Needed to support EMU8086 PRINT_NUM_UNS function 
END

每次运行此程序时,都应在1到10之间打印一个数字。从时钟滴答中获取随机值后,我们将其转换为1到10之间的数字。该代码与此伪代码相似 1

unsigned char num = (get_rand_value() % 10) + 1

我们除以10并使用modulo(Modulo值将在0到9之间),并添加1以使其值在1到10之间。get_rand_value实际上是INT 1AH/AH = 0系统调用。

注意:时钟刻度是一个半随机源,转换为1到10的方法的方法遭受Modulo偏置的影响。我将上面的代码作为一种快速而肮脏的方法提出,但应该足以让您开始分配。


可以在不发布 int 指令的情况下执行此操作,但是我们仍在通过对中断处理程序的代码进行间接调用来使用中断向量表。我怀疑这是您提出问题是否可以完成而不会中断时的想法。引擎盖下的An int 指令按下当前标志寄存器(使用 pushf ),然后是等效的。控制在0x0000处转移到远处的地址:[Interrupt_num * 4]中断矢量表(IVT)中。当中断例程完成时,它将发布IRET指令,该指令撤消推动,恢复标志并在远处呼叫后返回指令。修订的代码看起来像:

org 100h
include emu8086.inc                                                       
xor ax,ax            ; xor register to itself same as zeroing register
mov es,ax            ; Zero the ES register for use with FAR JMP below so that we
                     ;     can make a FAR CALL relative to bottom of Interrupt Vector Table
                     ;     in low memory (0x0000 to 0x03FF)
; Do a system call without the INT instruction
; This is advanced assembly and relies on the
; understanding of how INT/IRETD work. We fake a 
; system call by pushing FLAGS and rather 
; than use int 1ah we do a FAR CALL indirectly 
; through the interrupt vector table in lower memory
pushf                ; Push FLAGS
call far es:[1ah*4]  ; Indirectly call Int 1ah/ah=0 through far pointer in IVT
                     ;     get timer ticks since midnight in CX:DX
mov ax,dx            ; Use lower 16 bits (in DX) for random value
xor dx,dx            ; Compute randval(DX) mod 10 to get num
mov bx,10            ;     between 0 and 9
div bx
inc dx               ; DX = modulo from division
                     ;     Add 1 to give us # between 1 and 10 (not 0 to 9)
mov ax,dx            ; Move to AX to print
call PRINT_NUM_UNS   ; Print value in AX as unsigned
ret
DEFINE_PRINT_NUM_UNS ; Macro from include file to make PRINT_NUM_UNS usable
END

相关问题?可能的问题。简单的lcg prng

在这个问题的一天之内,还有一个模糊的问题。如果此任务与另一个分配有关,则需要注意,如果您尝试从系统计时器tick快速获取随机数,则会遇到问题。在上面的答案中,我说:

该值是半随机。您可以将其直接打印出来,但最好将其传递到伪随机数生成器(PRNG)作为种子值。

计时器分辨率为每秒18.2倍。这不是一个很高的分辨率,很可能会拨打 int 1Ah/ah = 0 一个接一个地将导致返回相同的号码,或者第二个呼叫具有更高的价值的机会首先。可以通过创建PRNG(如简单的LCG)来解决这一问题,然后使用一次计时器值将其播种。对于每个值,您需要 - 您查询下一个值而不是系统时间的PRNG。

可以在此相关的stackoverflow答案中找到一个简单的基于LCG的PRNG。基于该答案,您可以创建一个srandsystime函数以用计时器tick播种PRNG,以及从PRNG返回下一个值的rand()函数。下面的代码演示了一次设置种子,然后在1和10之间显示两个随机值:

org 100h
include emu8086.inc
start:
    call srandsystime   ; Seed PRNG with system time, call once only 
    call rand           ; Get a random number in AX
    call rand2num1to10  ; Convert AX to num between 1 and 10
    call PRINT_NUM_UNS  ; Print value in AX as unsigned
    PRINT ", "          ; Print delimiter between numbers
    call rand           ; Get another random number in AX
    call rand2num1to10  ; Convert AX to num between 1 and 10
    call PRINT_NUM_UNS  ; Print value in AX as unsigned
    ret 
; Return number between 1 and 10
;    
; Inputs:   AX = value to convert
; Return:   (AX) value between 1 and 10
rand2num1to10:
    push dx
    push bx
    xor dx,dx           ; Compute randval(DX) mod 10 to get num
    mov bx,10           ;     between 0 and 9
    div bx
    inc dx              ; DX = modulo from division
                        ;     Add 1 to give us # between 1 and 10 (not 0 to 9)
    mov ax,dx
    pop bx
    pop dx
    ret
; Set LCG PRNG seed to system timer ticks
;
; Inputs:   AX = seed
; Modifies: AX 
; Return:   nothing 
srandsystime:
    push cx
    push dx
    xor ax, ax          ; Int 1Ah/AH=0 to get system timer in CX:DX 
    int 1ah
    mov [seed], dx      ; seed = 16-bit value from DX
    pop dx
    pop cx
    ret
; Updates seed for next iteration
;     seed = (multiplier * seed + increment) mod 65536
;     multiplier = 25173, increment = 13849
;
; Inputs: none
; Return: (AX) random value
rand:
    push dx
    mov ax, 25173       ; LCG Multiplier
    mul word ptr [seed] ; DX:AX = LCG multiplier * seed
    add ax, 13849       ; Add LCG increment value
    mov [seed], ax      ; Update seed
    ; AX = (multiplier * seed + increment) mod 65536
    pop dx
    ret
        
seed dw 11             ; Default initial seed of 11    
    DEFINE_PRINT_NUM_UNS; Macro from include file to make PRINT_NUM_UNS usable    
END

脚注

  • 1 lowerupper(包含)范围内获取一个随机数您可以使用此通用公式:

    rndvalue =(rand()%(上低器 1)) 下;

  • 缺陷:将随机值从prng转换为1至10之间的数字仍然患有模型偏置。

  • i通常使用WATCOM的注册召集约定(第12页的描述)通常在开发16位组件例程时。可以根据自己的需求来量身定制。

  • 此特定的LCG PRNG在模式重复之前的周期约为65536。对于大多数简单的任务,这应该足够。

标准PC系统的配置为PIT通道0 fire每秒18.2次。每次发射时,都会发生中断,BIOS计算自系统启动以来发生的次数,并将此数字存储在地址0040:006c处的BIOS数据区域。您可以将此值用作程序的"随机"种子值:

mov ax,0040h
mov es,ax       ; load segment for BIOS data area
mov ax,es:006ch ; load number of IRQ8 since boot into ax

由于此值经常变化,因此似乎是随机的。我建议您稍微稍微散步,以免每次通话都增加。(即种子prng并进行一些迭代)。

我不确定EMU8086是否正确模拟了此功能,但是在PC上应该有效。

最新更新