1. Computing

Patrick van Logchem's Entry for the Unique Random Numbers Generator

By

Question: Patrick van Logchem's Entry for the Unique Random Numbers Generator
Challenge: a custom Delphi function, Randomizer, takes an open integer array and should fill it with unique random numbers as fast as possible.

The code is submitted for the Unique Random Numbers Delphi Challenge

Answer: Unique random number generator entry by Patrick van Logchem (The Netherlands):
 unit PatrickvanLogchem_Randomizer;
 
 interface
 
 
 (*
 The algorithm used is based on the
 "Galois Linear Feedback Shift Register (LFSR)". This algorithm
 generates a unique non-repeating sequence of numbers, which
 avoids the need to check for duplicate elements. A special case
 is available for a MaxValue that fits in a 32-bit variable.
 
 Sources :
 http://en.wikipedia.org/wiki/Linear_feedback_shift_register
 http://tog.acm.org/GraphicsGems/gems/Dissolve.c
 http://www.mactech.com/articles/mactech/Vol.06/06.12/SafeDissolve/index.html
 *)
 procedure Randomizer(const MaxValue: Int64; var Values: array of Int64) ;
 
 implementation
 
 // Disables loop-unrolling; shortens code, slows down a little.
 {$DEFINE RANDOMIZER_UseLoopUnrolling}
 
 {$INLINE ON}
 {$RANGECHECKS OFF}
 {$OPTIMIZATION ON}
 {$OVERFLOWCHECKS OFF}
 
 // Note : If the compiler complains about UInt64,
 // uncomment the next line:
 // type UInt64 = Int64;
 
 // No bits set returns 0, left-most bit returns 32, right-most bit returns 1
 function GetLeftmostBitNr(const aValue: Cardinal): Integer;
 asm
   CMP EAX, 0
   JE @@1
 
   BSR EAX, EAX
   INC EAX
 @@1:
 end;
 
 procedure Randomizer(const MaxValue: Int64; var Values: array of Int64) ;
 const
   // Masks to generate the pseudo-random sequence.
   // The table runs 0..32, but only elements 2..32 are valid.
   SeqMasks32: array [0..32] of Cardinal = ($00000000,
     $00000000, $00000003, $00000006, $0000000C, $00000014, $00000030, $00000060, $000000B8,
     $00000110, $00000240, $00000500, $00000CA0, $00001B00, $00003500, $00006000, $0000B400,
     $00012000, $00020400, $00072000, $00090000, $00140000, $00300000, $00400000, $00D80000,
     $01200000, $03880000, $07200000, $09000000, $14000000, $32800000, $48000000, $A3000000) ;
   // Masks 32..64
   SeqMasks64: array [1..32] of UInt64 = (
     $0000000100080000, $0000000204000003, $0000000500000000, $0000000801000000,
     $000000100000001F, $0000002000000031, $0000004400000000, $000000A000140000,
     $0000012000000000, $00000300000C0000, $0000063000000000, $00000C0000030000,
     $00001B0000000000, $0000300003000000, $0000420000000000, $0000C00000180000,
     $0001008000000000, $0003000000C00000, $0006000C00000000, $0009000000000000,
     $0018003000000000, $0030000000030000, $0040000040000000, $00C0000600000000,
     $0102004000000000, $0200004000000000, $0600003000000000, $0C00000000000000,
     $1800300000000000, $3000000000000030, $6000000000000000, $D800000000000000) ;
 
   procedure _Randomizer32(const MaxValue: Cardinal) ;
   var
     Mask: Cardinal;
     Current: Cardinal;
     ValuePtr: PAnsiChar;
     k: Integer;
   begin
     Mask := SeqMasks32[GetLeftmostBitNr(MaxValue)];
     Current := 0;
     ValuePtr := @(Values[0]) ;
     k := Length(Values) ;
 {$IFDEF RANDOMIZER_UseLoopUnrolling}
     while k > 4 do
     begin
       repeat
         Current := (Current shr 1) xor (Mask and Cardinal(-1+(Current and 1))) ;
       until Current < MaxValue;
       PCardinal(ValuePtr+(0 * SizeOf(Cardinal)))^ := 1 + Current;
       PCardinal(ValuePtr+(1 * SizeOf(Cardinal)))^ := 0;
 
       repeat
         Current := (Current shr 1) xor (Mask and Cardinal(-1+(Current and 1))) ;
       until Current < MaxValue;
       PCardinal(ValuePtr+(2 * SizeOf(Cardinal)))^ := 1 + Current;
       PCardinal(ValuePtr+(3 * SizeOf(Cardinal)))^ := 0;
 
       repeat
         Current := (Current shr 1) xor (Mask and Cardinal(-1+(Current and 1))) ;
       until Current < MaxValue;
       PCardinal(ValuePtr+(4 * SizeOf(Cardinal)))^ := 1 + Current;
       PCardinal(ValuePtr+(5 * SizeOf(Cardinal)))^ := 0;
 
       repeat
         Current := (Current shr 1) xor (Mask and Cardinal(-1+(Current and 1))) ;
       until Current < MaxValue;
       PCardinal(ValuePtr+(6 * SizeOf(Cardinal)))^ := 1 + Current;
       PCardinal(ValuePtr+(7 * SizeOf(Cardinal)))^ := 0;
 
       Inc(ValuePtr, 8 * SizeOf(Cardinal)) ;
 
       Dec(k, 4) ;
     end;
 {$ENDIF RANDOMIZER_UseLoopUnrolling}
 
     while k > 0 do
     begin
       repeat
         Current := (Current shr 1) xor (Mask and Cardinal(-1+(Current and 1))) ;
       // Until we're within range :
       until Current < MaxValue;
 
       PCardinal(ValuePtr + (0 * SizeOf(Cardinal)))^ := 1 + Current;
       PCardinal(ValuePtr + (1 * SizeOf(Cardinal)))^ := 0;
       Inc(ValuePtr, 2 * SizeOf(Cardinal)) ;
 
       Dec(k) ;
     end;
   end;
 
 begin
   if Cardinal(MaxValue shr 32) = 0 then
     _Randomizer32(Cardinal(MaxValue))
   else
     _Randomizer64;
 end;
 
 end.
 
Download Patrick's Unique Randomizer code.

Test data:
- array size: 10 000
- number range: 1 - 100 000

Patrick's speed result: 99 microseconds !!

Explore the list of all accepted entries for the Fastest Unique Random Number Generator Delphi challenge.

Readers Respond: Your Delphi Programming Challenges

©2014 About.com. All rights reserved.