Ensuring unbiased number generation within a range using RDRAND instruction

163 Views Asked by At

Working on a code golf challenge which requires using the RDRAND hardware random number generator.

Examples

Set r12 to a random number [0,255]

rdrand ax
movzx r12,al

Set r8 to a random number [0,1]

rdrand r8
and r8,1

Question

I'm assuming the above examples are non-biased?

But, suppose the challenge required generating a random number in the range [0,N], say N=512, are there any biases introduced? If so, what's the correct assembly code to generate a non-biased random number from [0,N]?

Update

Based upon the comment

For power-of-2 ranges, just mask out the bits you want.

Perhaps this example will work by looking at a 2^10 range then masking the bits?

    ; Generate a random number until it is in the range [0, 512]
    mov dword[num], 0
retry0:
    rdrand eax
    jnc retry0
    and eax, 1023 ; Limit the range to [0, 1023] to fit into 10 bits
    cmp eax, 512  ; Check if the number is within [0, 512]. (Rejection Sampling)
    ja retry0     ; If above 512, generate again

    mov [num], eax
1

There are 1 best solutions below

8
vengy On

As suggested in the comments section, here's my attempt at converting Lemire's Method to Nasm. It outputs a random 32-bit number from [0,N].

Basically, took Peter's C godbolt example and ported it to a Nasm example

; Output a random number from [0,N]
;
; Debiased Integer Multiplication — Lemire's Method
;
; uint32_t bounded_rand(rng_t& rng, uint32_t range) {
;     uint32_t t = (-range) % range;
;     do {
;         uint32_t x = rng();
;         uint64_t m = uint64_t(x) * uint64_t(range);
;         uint32_t l = uint32_t(m);
;     } while (l < t);
;     return m >> 32;
; }


section .bss
    resb 10     
    num_str:

section .text
global _start

_start:

    ; Generate a random number until it is in the range [0, 8192]
    ; -----------------------------------------------------------
    mov     edi, 8192
    inc     edi       ; [0, 8193)
    mov     eax, edi
    xor     edx, edx
    neg     eax
    div     edi
.retry:
    rdrand  eax
    jnc     .retry
    imul    rax, rdi
    cmp     eax, edx
    jb      .retry
    shr     rax, 32

    ; At this point, EAX contains the unbiased random number
    ; -----------------------------------------------------------

    ; Convert number into ASCII
    mov rsi, num_str
    mov ecx, 10
.loop:
    xor edx, edx
    div ecx
    add dl, 48
    dec rsi
    mov [rsi], dl
    test eax, eax
    jnz .loop
    ; Write number
    mov rax, 1
    mov rdi, 1
    mov rdx, num_str
    sub rdx, rsi
    syscall
    ; Exit
    mov rax, 60
    xor rdi, rdi
    syscall

Let me know if there are any glaring mistakes. Thanks.