组装中的随机数 [英] Random number in assembly

查看:74
本文介绍了组装中的随机数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

解决方案

如果您使用的是DOS的实际版本(而不是EMU8086),则可以使用@fuz方法,它不需要中断.您只需在 BIOS数据区(BDA).该位置的值是一个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,然后使用模(模值将在0到9之间),然后加1使其成为1到10之间的值.get_rand_value实际上是Int 1ah/AH = 0系统调用./p>

注意:时钟滴答是一个半随机的信号源,转换为1到10的值的方法受 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

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

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

计时器分辨率为每秒18.2次.分辨率不是很高,一个接一个地调用 Int 1ah/ah = 0 可能会导致返回相同的数字,或者第二个调用返回的值大于返回的值的机会更大.首先.可以通过创建PRNG(例如简单的 LCG )来解决此问题,并使用一次计时器值播种.对于您需要的每个值-您都向PRNG查询下一个值而不是系统时间.

可以在此相关的文档中找到一个简单的基于LCG的PRNG Stackoverflow答案.根据该答案,您可以创建一个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()%(upper-lower + 1))+ lower;

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

  • 我使用 Watcom的注册调用约定(第12页提供了说明),通常是在开发16位汇编例程时使用.可以根据自己的需求量身定制.

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

I am new to assembly and would like to know how to write a program in EMU8086 that prints a different random number in every run of it. Is it possible to do it without using interrupts?

解决方案

If you were using a real version of DOS (not EMU8086) @fuz method is the way you can do it, and it doesn't require interrupts. You just read the lower 16-bits of the 32-bit value at memory address 0x46c (0x00040:0x006c) in the BIOS Data Area(BDA). The value at that location is a 32-bit value representing the number of timer ticks since midnight. Unfortunately EMU8086 doesn't support this method.

To get a random number in EMU8086 with interrupts (system call) you can use Int 1ah/ah=0h:

TIME - GET SYSTEM TIME

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

You can then use that value and print it out. The value is semi random. You can print it out directly but it is preferable to pass it into a Pseudo-random Number Generator (PRNG) as a seed value. See section below for a basic LCG. Printing an integer is a separate issue although EMU8086 has a macro/function to do that. This code could produce a semi-random number between 1 and 10 and print it:

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

Each time you run this program it should print a number between 1 and 10. After we get the random value from the clock ticks we convert it to a number between 1 and 10. The code would have been similar to this pseudo code1:

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

We divide by 10 and use the modulo (Modulo value will be between 0 and 9) and add 1 to make it a value between 1 and 10. get_rand_value is effectively the Int 1ah/AH=0 system call.

Note: The clock ticks is a semi random source, and the method to convert to a value from 1 to 10 suffers from modulo bias. I present the code above as a quick and dirty method but should be enough to get you started on your assignment.


It is possible to do this without issuing an INT instruction, but we are still using the interrupt vector table by doing an indirect FAR CALL to the code for the interrupt handler. I doubt this is what you had in mind when you asked the question whether it can be done without interrupts. An INT instruction under the hood pushes the current FLAGS register (using PUSHF) followed by the equivalent of a FAR CALL. Control is transferred to the FAR address at 0x0000:[interrupt_num * 4] which is in the interrupt vector table (IVT). When the interrupt routine finishes it will issue an IRET instruction which undoes the pushes, restores the flags and returns the instruction after the FAR CALL. The revised code could look like:

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


Related Question? Possible Issues. Simple LCG PRNG

There is another question vaguely similar to this that was posted within a day of this one. If this assignment is related to the other one then it needs to be noted that you will encounter issues if you attempt to get random numbers in quick succession from the system timer tick. In my answer above I stated:

The value is semi random. You can print it out directly but it is preferable to pass it into a Pseudo Random Number Generator (PRNG) as a seed value.

The timer resolution is 18.2 times a second. That isn't a very high resolution and likely calling Int 1ah/ah=0 one after the other will result in the same number being returned or the second call having a higher chance of returning a higher value than the first. This can be resolved by creating a PRNG (like a simple LCG) and use the timer value once to seed it. For each value you need - you query the PRNG for the next value not the system time.

A simple LCG based PRNG can be found in this related Stackoverflow Answer. Based on that answer you can create an srandsystime function to seed the PRNG with the timer ticks, and a rand() function that returns the next value from the PRNG. The code below demonstrates setting the seed once, and then displaying two random values between 1 and 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

Footnotes:

  • 1To get a random number in the range lower to upper (inclusive) you can use this general formula:

    rndvalue = (rand() % (upper-lower+1)) + lower;

  • Deficiency: converting the random value from the PRNG to a number between 1 and 10 still suffers from modulo bias.

  • I use Watcom's register calling convention (Page 12 for a description) when developing 16-bit assembly routines in general. That can be tailored to one's own needs.

  • This particular LCG PRNG has a period of about 65536 before the pattern repeats. This should be enough for most simple tasks.

这篇关于组装中的随机数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆