;name: congruentialrng.asm
;build: nasm -felf64 congruentialrng.asm -o congruentialrng.o
;       ld -melf_x86_64 -o congruentialrng congruentialrng.o 
;purpose: Create pseudo random number x in an interval [a,b] using the 
;         congruential pseudo random generator from George Marsaglia.
;         https://en.wikipedia.org/wiki/Linear_congruential_generator

bits 64

[list -]
    %include "unistd.inc"
[list +]

;255 is maximum number for this program
%define MAXNUMBERS  255
%define LF          10

section .bss
    buffer:         resb 1
section .data
    random:         db       "0"
    .len:           equ      $-random
    ; the table array to store the frequency of each individual number
    table:          db       0,0,0,0,0,0,0,0,0
    tableline1:     db       "number of generated "
    .len:           equ      $-tableline1
    tableline2:     db       "'s : "
    .len:           equ      $-tableline2
    seed:           dq       0                       ;the seed
section .text

global _start        

    mov     rcx,MAXNUMBERS                 ;initialize outer-loop counter
    call    initRandomGenerator
    push    rcx
    mov     rdi,random                     ;bufferaddress in RDI
    ; generate numbers in the interval [1,9]
    mov     rax,1                          ;lower boundary of interval
    mov     rdx,9                          ;higher boundary of interval
    call    generateRandom
    ; adjust table with counts for each generated number
    mov     rdx,rax                        ;number in RDX
    mov     rsi,table
    add     rsi,rdx                        ;point to the right place in the table
    dec     rsi
    inc     byte [rsi]                     ;increment position in table
    ; convert the number to ASCII
    ; depending the number len needed we convert the number of needed bits in ASCII hexadecimal
    ; in this example I only use the lowest nibble of RAX since the interval is [5,9]
    call    nibble2hexascii
    syscall write,stdout,random,random.len     
    pop     rcx                            ;restore outer-loop counter
    dec     rcx
    and     rcx,rcx
    jz      printStatistics
    push    rcx                            ;syscall changes RCX !!
    mov     al,","
    call    writeChar
    pop     rcx
    jmp     nextRandom
    mov     al,LF
    call    writeChar
    mov     rcx,9
    mov     rsi,table
    push    rsi
    mov     rsi,tableline1
    mov     rdx,tableline1.len
    call    writeLine      
    mov     rax,10
    sub     rax,rcx
    call    byte2decascii
    call    writeDecimal
    mov     rsi,tableline2
    mov     rdx,tableline2.len
    call    writeLine
    pop     rsi
    xor     rax,rax
    call    byte2decascii
    call    writeDecimal
    mov     al,10
    call    writeChar
    loop    nextLine
    syscall exit,0

    rol     rdx,32
    or      rax,rdx
    mov     qword[seed],rax
; in RAX :: lower boundary of interval
;    RDX :: higher boundary of interval
    ; calculate interval len
    dec     rax                            ;minus one so we can have value for lower boundary too
    push    rax                            ;save lower boundary
    sub     rdx,rax                        ;calculate interval len
    mov     rbx,rdx                        ;put in RBX
    call    congruentialrng
    ; create random number rax <= number <= rdx
    xor     rdx,rdx
    idiv    rbx                            ;RAX = RAX \ RBX
    cmp     rdx,0
    jnz     .skip
    add     rdx,rbx
    pop     rax                            ;restore lower boundary
    add     rdx,rax                        ;add lower boundary to random number
    mov     rax,rdx                        ;RAX is number in interval [RAX,RDX]
    ; Multiply with carry algorithm
    push    rbx
    push    rdx
    xor     rdx,rdx
    mov     rax,qword[seed]
    mov     rbx,69069
    mul     rbx
    add     rax,362437                     ;RAX is the random number
    mov     qword[seed],rax                ;and also new seed
    pop     rdx
    pop     rbx
    ; write a decimal in EAX to STDOUT
    ror     eax,16
    and     al,al
    jz      gettenthts
    call    writeChar
    rol     eax,8
    and     al,al
    jz      getdigits
    call    writeChar
    rol     eax,8
    call    writeChar
    ; write a character in AL to STDOUT
    push    rdx
    push    rsi
    mov     byte[buffer],al
    mov     rdx,1
    mov     rsi,buffer
    jmp     _write    
    ; write a string pointed by RSI with len RDX to STDOUT
    push    rdx
    push    rsi
    push    rdi
    push    rcx
    push    rax
    syscall write,stdout
    pop     rax
    pop     rcx
    pop     rdi
    pop     rsi
    pop     rdx
; convert a byte in AL to its decimalk ASCII equivalent in EAX
; in  :: AL  = byte or 8 bits
; out :: EAX = lowest 24 bits are Decimal ASCII of byte
;int 3
    push    rbx
    push    rcx
    push    rdx
    push    r8
    xor     rcx,rcx
    xor     r8,r8
    mov     rbx,10
    xor     rdx,rdx
    idiv    rbx
    or      dl,"0"
    shrd    r8,rdx,8
    inc     rcx
    and     al,al
    jnz     .repeat
    shl     rcx,3
    shld    rax,r8,cl
    pop     r8
    pop     rdx
    pop     rcx
    pop     rbx
; convert a nibble in AL to its hexadecimal ASCII equivalent in AL
; in  :: AL = NIBBLE or 4 bits (least significant)
; out :: AL = Hexadecimal ASCII of NIBBLE
    and     al,0x0F
    or      al,"0"
    cmp     al,"9"
    jbe     .done
    add     al,7