如果您使用的是真实版本的DOS(不是EMU8086),那么@fuz方法就是您可以做到的方式,并且它不需要中断.您只需在BIOS数据区(BDA)中的内存地址0x46c(0x00040:0x006c)处读取32位值的低16位.该位置的值是一个32位值,表示自午夜以来的计时器滴答数.不幸的是,EMU8086不支持这种方法.
要在EMU8086中获取带有中断的随机数(系统调用),您可以使用Int 1ah/ah = 0h:
时间 - 获取系统时间
Run Code Online (Sandbox Code Playgroud)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
Run Code Online (Sandbox Code Playgroud)
每次运行此程序时,它应该打印一个介于1和10之间的数字.在我们从时钟滴答中获取随机值后,我们将其转换为1到10之间的数字.代码与此伪代码类似:
unsigned char num = (get_rand_value() % 10) + 1
Run Code Online (Sandbox Code Playgroud)
我们除以10并使用模数(模数值将介于0和9之间)并加1以使其成为1到10之间的值.get_rand_value实际上是Int 1ah/AH = 0系统调用.
注意:时钟滴答是一个半随机源,并且转换为1到10之间的值的方法存在模偏差.我将上面的代码作为一种快速而肮脏的方法提供,但应该足以让您开始完成任务.
可以在不发出INT指令的情况下执行此操作,但我们仍然通过对中断处理程序的代码执行间接FAR CALL来使用中断向量表.当你提出问题是否可以在没有中断的情况下完成时,我怀疑这是你的想法.一个INT引擎盖下指令将当前FLAGS(使用寄存器PUSHF),接着是FAR CALL的等价物.控制转移到0x0000的FAR地址:[interrupt_num*4],它位于中断向量表(IVT)中.当中断例程完成时,它将发出一个IRET指令,撤销推送,恢复标志并在FAR CALL之后返回指令.修改后的代码可能如下所示:
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
Run Code Online (Sandbox Code Playgroud)
还有一个与此相似的问题是在这一天内发布的.如果此分配与另一个分配相关,则需要注意的是,如果您尝试从系统计时器滴答中快速连续获取随机数,则会遇到问题.我在上面的回答中说:
该值是半随机的.您可以直接打印出来,但最好将其作为种子值传递给伪随机数生成器(PRNG).
定时器分辨率为每秒18.2次.这不是一个非常高的分辨率,并且可能一个接一个地调用Int 1ah/ah = 0将导致返回相同的数字,或者第二个调用返回比第一个更高的值的机会更高.这可以通过创建PRNG(如简单的LCG)并使用计时器值一次来播种来解决.对于您需要的每个值 - 您查询PRNG以获取下一个值而不是系统时间.
在这个相关的Stackoverflow Answer中可以找到一个简单的基于LCG的PRNG.根据该答案,您可以创建一个函数,使用计时器滴答对PRNG进行种子处理,以及从PRNG返回下一个值的函数.下面的代码演示了如何设置种子一次,然后显示1到10之间的两个随机值:srandsystimerand()
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
Run Code Online (Sandbox Code Playgroud)
备注:
| 归档时间: |
|
| 查看次数: |
2366 次 |
| 最近记录: |