gpt4 book ai didi

assembly - 组装中的随机数

转载 作者:行者123 更新时间:2023-12-04 23:14:04 26 4
gpt4 key购买 nike

我是汇编语言的新手,想知道如何在EMU8086中编写一个程序,该程序在每次运行时均打印不同的随机数。是否可以在不使用中断的情况下做到这一点?

最佳答案

如果您使用的是DOS的真实版本(不是EMU8086),则@fuz方法是您可以使用的方法,并且它不需要中断。您只需在BIOS Data Area(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


然后,您可以使用该值并打印出来。该值是半随机的。您可以直接将其打印出来,但是最好将其作为种子值传递到 Pseudo-random Number Generator(PRNG)中。参见下面的 basic LCG部分。尽管EMU8086具有 macro/function来执行操作,但打印整数是一个单独的问题。这段代码可以产生一个介于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并使用模(模值将在0到9之间),然后加1使其成为1到10之间的值。 get_rand_value实际上是Int 1ah / AH = 0系统调用。
注意:时钟滴答是半随机源,并且转换为1到10的值的方法受 modulo bias的影响。我将上面的代码作为一种快速而肮脏的方法进行介绍,但足以使您开始进行作业。

可以在不发出 INT指令的情况下执行此操作,但是我们仍然通过对中断处理程序的代码执行间接FAR CALL来使用中断向量表。当您问这个问题是否可以不间断地完成时,我想这就是您的初衷。底层的INT指令推入当前的FLAGS寄存器(使用 PUSHF),后跟FAR CALL的等效项。控制权转移到中断向量表(IVT)中的0x0000:[interrupt_num * 4]的FAR地址。中断例程完成后,它将发出 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


相关问题?可能的问题。简单的LCG PRNG
还有一个与此相似的问题,在这个问题的一天之内就是 posted。如果此分配与另一个分配有关,则需要注意的是,如果尝试从系统计时器滴答声中快速连续获取随机数,则会遇到问题。在上面的回答中,我说:

该值是半随机的。您可以直接将其打印出来,但是最好将其作为种子值传递到 Pseudo Random Number Generator(PRNG)中。

计时器分辨率为每秒18.2次。分辨率不是很高,一个接一个地调用Int 1ah / ah = 0可能会导致返回相同的数字,或者第二个调用返回的值比第一个更大的机会。这可以通过创建PRNG(如简单的 LCG)并使用一次计时器值作为种子来解决。对于您需要的每个值-您向PRNG查询下一个值而不是系统时间。
在相关的 LCG based PRNG中可以找到一个简单的 Stackoverflow Answer。根据该答案,您可以创建一个 srandsystime函数以用计时器滴答声为PRNG注入种子,并创建一个 rand()函数以从PRNG返回下一个值。下面的代码演示了一次设置种子,然后显示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()%(up-lower + 1))+更低;


缺陷:将PRNG中的随机值转换为1到10之间的数字仍然会遭受模偏差。

通常在开发16位汇编例程时使用 Watcom's register calling convention(描述为第12页)。可以根据自己的需求量身定制。

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

关于assembly - 组装中的随机数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47607104/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com