;name: xorshift.asm
;description: Create pseudo random number x in an interval [a,b] using the
;             XorShift pseudo random generator from George Marsaglia
;             http://en.wikipedia.org/wiki/Xorshift
;             at the end a list of generated numbers is shown
;example output:
;number of generated 1's : 23
;number of generated 2's : 29
;number of generated 3's : 36
;number of generated 4's : 29
;number of generated 5's : 30
;number of generated 6's : 20
;number of generated 7's : 23
;number of generated 8's : 34
;number of generated 9's : 31

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 array to store the frequency of each individual number
    table:          times 9 db 0
    tableline1:     db  "number of generated "
    .len:           equ $-tableline1
    tableline2:     db  "'s : "
    .len:           equ $-tableline2
section .text

global _start       
    mov     rcx,MAXNUMBERS                  ;initialize outer-loop counter
    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 length 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 [1,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     .repeat
    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,LF
    call    writeChar
    loop    .nextLine
    syscall exit,0
;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
    ; generate seed
    rdtsc                   ;read cpu time stamp counter
    rol     rdx,32          ;mov EDX in high 32 bits of RAX
    or      rax,rdx         ;RAX = seed
    call    xorShift        ;get a pseudo random number from seed
    ; 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]

;XORSHIFT algorithm
    mov     rdx,rax        
    shl     rax,13
    xor     rax,rdx
    mov     rdx,rax
    shr     rax,17
    xor     rax,rdx
    mov     rdx,rax
    shl     rax,5
    xor     rax,rdx         ;rax random 64 bit value
;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     writeLine.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
    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     .l1
    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     .l1
    add     al,7