AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Generating unique random integers
Goto page Previous  1, 2, 3, 4, 5, 6  Next
 
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions
View previous topic :: View next topic  
Author Message
jetskijoe



Joined: 02 Jun 2006
Posts: 7

PostPosted: Mon Jun 11, 2007 3:52 am    Post subject: Reply with quote

Here is running with 1 thread and just doing 10 stores:

the [store] is in milliseconds is my timing per store

[info]
Starttime=20070610234855
Endtime=20070610235053
totaldays=0.001370
totalhours=0.032882
totalmins=1.972917
totalseconds=118.375000
totalmilliseconds=118375
[store]
10101=4422
10102=4219
10103=3953
10104=3937
10105=3516
10106=18765
10107=18047
10108=17235
10109=16781
10110=17484
Back to top
View user's profile Send private message MSN Messenger
Laszlo



Joined: 14 Feb 2005
Posts: 4078
Location: Pittsburgh

PostPosted: Mon Jun 11, 2007 3:59 am    Post subject: Reply with quote

It means it would finish in 40 minutes (200 stores)? Faster than I expected. Thanks for the detailed info!
Back to top
View user's profile Send private message
jetskijoe



Joined: 02 Jun 2006
Posts: 7

PostPosted: Mon Jun 11, 2007 4:08 am    Post subject: Reply with quote

Yes I will post the total time tomorrow or later tonight if it finishes before I go to bed.
finished with 200 stores and 100,000 items in each store Smile below is the stats:
[edited]
Starttime=20070610235731
Endtime=20070611004949
totaldays=0.036311
totalhours=0.871467
totalmins=52.288017
totalseconds=3137.281000
totalmilliseconds=3137281

The following is the code I like to add to each of my scripts at the top and bottom to get time times:

Code:

#SingleInstance Force
#NoEnv
SetBatchLines -1

start_time := A_TickCount
IniWrite, %A_Now%, time.ini, info, Starttime

stores    = 2
estore    = 100000
totalitem = 200000
startitem = 100000
Total_qty = 100
Store_no  = 10000
List = 0
Loop % totalitem-1 {
   List .= "`n" . A_Index
}

Loop %stores% {
      start_store_loop := A_TickCount
   itemno =
   itemqutxt =
   stockitemtxt =
   storetxt =
   Sort List, Random
   Loop Parse, List, `n
      IfGreater A_Index,%estore%, Break
      Else {
         Random Total_qty, 75, 100
         itemno := A_LoopField + startitem
         itemqutxt = %itemqutxt%`n%itemno%,%Store_no%,%Total_qty%
         num%itemno% += 1
      }
   FileAppend %itemqutxt%, itemqu.txt
   store_no += 1
   elapsed_store_loop := A_TickCount - start_store_loop
   IniWrite, %elapsed_store_loop%, time.ini, store, %store_no%   
}

Loop %totalitem% {
    itemno := startitem + A_Index
    If (num%itemno% <> "")
       items .= itemno . " = " . num%itemno% . "`n"
}
FileAppend %items%, itemcount.txt

Totaltime := A_TickCount - start_time
totalseconds += (Totaltime / 1000)
totalmins += (Totaltime / 60000)
totalhours += (Totaltime / 3600000)
totaldays += (Totaltime / 86400000)
IniWrite, %A_Now%, time.ini, info, Endtime
IniWrite, %totaldays%, time.ini, info, totaldays
IniWrite, %totalhours%, time.ini, info, totalhours
IniWrite, %totalmins%, time.ini, info, totalmins
IniWrite, %totalseconds%, time.ini, info, totalseconds
IniWrite, %totaltime%, time.ini, info, totalmilliseconds


Thanks
Joe
Back to top
View user's profile Send private message MSN Messenger
Laszlo



Joined: 14 Feb 2005
Posts: 4078
Location: Pittsburgh

PostPosted: Sun Jul 01, 2007 5:07 pm    Post subject: Reply with quote

Laszlo wrote:
PhiLho wrote:
a mathematical function to generate semi-random numbers without repetition
Take any invertible function in the range [0,n-1], and generate f(0), f(1),...f(k). If k < n, the function values are all different. The difficulty is to pick a function, which behaves (pseudo) randomly, that is, common statistical tests find the generated sequence random.

There is another alternative: a recursion x ← f(x). If the function is chosen appropriately, x will cycle through all possible values. One well studied example is f(x) = A*x + B mod M. For a general modulus the conditions for A and B (to achieve a cycle length of M, the maximum) are quite complicated, requiring a long and convoluted initialization routine. For a power of two modulus M2 the conditions are easy: A = 4k+1 and B = 2m+1 form. However, the unique returned values are not in the desired range. If we choose M2 the next largest power of two above M, we can just discard generated values, if they happen to be too large. In the average, at most one value is discarded at each call, which is pretty fast. (For the ultimate speed, one can implement a search for proper A and B values, and use the original modulus M.)

Don't forget, these linear congruential generators are not as good as AHK's built in Mersenne Twister random number generator, although we can have a larger number of different sequences.

At initialization any number for A, B and X0 (initial value) can be provided, the script transforms them to the allowable form. It the range R = max-min+1 is n bit long, the LS n-2 bits of A is used, the LS n-1 bits of B are used, and different X0 provide R different starting (hidden) values. For large ranges these provide many different sequences. For example, at R = 2**32 the seed material is 93 bits, so the number of different sequences are much larger than with the built in Random function (accepting only 32 bit seeds).

For simplicity the script below does not handle a range larger then 2**62, or smaller than 64. For the later case the simple quadratic time algorithms posted before should be fast enough.

Here is the corresponding script, finishing in about 488ms (2GHz Centrino, XP SP2):
Code:
t := A_TickCount
LcRand(100000,200000,56789,987654,44) ; initialization
Loop  100001
   s := LcRand() ; use the returned unique pseudorandom value
MsgBox % A_TickCount-t ; for benchmark only

LcRand(min="",max="",A0="",B0="",x0="") { ; 2**62 >= range = max-min+1 >= 64
   Static A, B, c, M, R, x
   If min =
      Loop
         If (R > x := Mod(A*x+B,M))
            Return c+x
   R := max - min + 1                   ; range
   MS:= DllCall("ntdll\RtlFindMostSignificantBit","Int64",R) ; can use loop to shift left R until overflow
   MS+= R > 1<<MS                       ; MS digit pos of next power of two, R <= 2**62
   M := 1<<MS                           ; Next power of two
   A := (A0 << 2 | 1) & (M-1)           ; 2 LS bits of A must be 01 for maximal cycle
   A |= A < 1<<(MS//3) ? 1<<(MS//3) : 0 ; Prevent too small A; MS >= 6
   B := (B0 << 1 | 1) & (M-1)           ; LS bit of B must be 1 for maximal cycle
   c := min
   x := x0
}
Back to top
View user's profile Send private message
Laszlo



Joined: 14 Feb 2005
Posts: 4078
Location: Pittsburgh

PostPosted: Tue Jul 03, 2007 12:03 am    Post subject: Reply with quote

NOTES for the linear congruential generator above:

(1) There is a slight speedup possibility, if we replace "Mod(x,M)" with "x & mask", when M is a power of two, and mask = M – 1. The acceleration is barely measurable (~1%).

(2) We could give an error message, if LcRand is initialized with a too small or too large range.

(3) The main weakness of the linear congruential generator is the regularity of the LS bits. In case of a power of two modulus, the last bit is alternating, the second last bit has a cycle of 4, etc. This regularity is not as strong with general moduli, but for more demanding applications we could improve the complexity of the sequence, which, of course, somewhat slows down the random number generation. One possibility is to use a larger modulus. This results in discarding more generated numbers. The MS bits tell, which value will be discarded, so it is very unpredictable, improving the randomness significantly. The slowdown is moderate: 50..70%.
Code:
LcDropRand(min="",max="",A0="",B0="",x0="",better=0) { ; 2**62 >= range = max-min+1 >= 64
   Static A, B, c, M, R, x
   If min =
      Loop
         If (R > x := A*x+B & M)
            Return c+x
   R := max - min + 1                   ; range
   If (R < 64 || R > 1<<62) {
      MsgBox Range (%R%) must be at least 64,`n at most 4611686018427387904 = 2**62
      Return ERROR
   }
   MS:= DllCall("ntdll\RtlFindMostSignificantBit","Int64",R) ; can use loop to shift left R until overflow
   MS+= R > (1<<MS)                     ; MS digit pos of next power of two, R <= 2**62
   M := (1<<(MS+!!better)) - 1          ; Mask from the Next power of two
   A := (A0 << 2 | 1) & M               ; LS 2 bits of A must be 01 for maximal cycle
   A |= A < 1<<(MS//3) ? 1<<(MS//3) : 0 ; Prevent too small A; MS >= 6
   B := (B0 << 1 | 1) & M               ; LS bit of B must be 1 for maximal cycle
   c := min
   x := x0
}
Testing on 100,001 numbers between 100,000 and 200,000 the running time increased from 488 ms to 787 ms (2GHz Centrino, 1GB RAM, XP SP2), but there is no noticeable pattern in the low order bits any more. Specifying 0 for the last parameter ("better") in the initialization call, or leaving it out reverts the function to the original, making it faster but less random.

(4) Another improvement of the randomness can be achieved with an invertible function, applied to the intermediate value A*x+B & M. By mixing the bits, simple periodicities can be removed. Since only the least significant bits exhibit short periods, shifting x to the right by a few places, and XOR it to x, breaks this periods. We can repeat this as many times as we want. It is easy to see that this function is invertible: the most significant bits are unchanged; with their help we can reconstruct the original values of the next few bits, which help finding the original values of the bits further to the right, and so on. Experiments showed that 2 shift-XOR steps mix the bits sufficiently:
Code:
LcMixRand(min="",max="",A0="",B0="",x0="") { ; 2**62 >= range = max-min+1 >= 64
   Static A, B, c, M, R, x, s1, s2
   If min =
      Loop {
         x := A*x+B & M
         If (R > y := x ^ x>>s1 ^ x>>s2)
            Return c+y
      }
   R := max - min + 1                   ; range
   If (R < 64 || R > 1<<62) {
      MsgBox Range (%R%) must be at least 64,`n at most 4611686018427387904 = 2**62
      Return ERROR
   }
   MS:= DllCall("ntdll\RtlFindMostSignificantBit","Int64",R) ; can use loop to shift left R until overflow
   MS+= R > (1<<MS)                     ; MS digit pos of next power of two, R <= 2**62
   s1:= MS//3, s2:= MS-s1               ; Shift values for mixing
   M := (1<<MS) - 1                     ; Mask from the Next power of two
   A := (A0 << 2 | 1) & M               ; LS 2 bits of A must be 01 for maximal cycle
   A |= A < 1<<s1 ? 1<<s1 : 0           ; Prevent too small A; MS >= 6
   B := (B0 << 1 | 1) & M               ; LS bit of B must be 1 for maximal cycle
   c := min
   x := x0
}
The running time for the same test as before increased to 716 ms, less than 50% more than the original.

(5) There is the question: what should these functions do, if called more times, than there are numbers between min and max. One could re-initialize the generator with a different seed, but these functions restart the same sequence. It has the nice property that at any time the last max-min+1 values generated are unique (until the user re-initializes the generator).

(6) Any initial seed value (A, B, X) is valid, and most of them will lead to very different random sequences. In case you want unpredictable seeds, the internal system timer can be used as part of the seed. Windows mixes this with information specific to the user and to the computer, and provides a Globally Unique Identifier. At each request a completely new one is generated, which is perfect for seeding. For example:
Code:
VarSetCapacity(U,16)
DllCall("rpcrt4\UuidCreate",Str,U)
A := NumGet(U,0,"Int64")
B := NumGet(U,4,"Int64")
X := NumGet(U,8,"Int64")
The performance counter provides a timer value with sub-microsecond resolution (it is stroed in the Cnt variable by the dll call below). Its last 4..7 digits are quite unpredictable at the first query (and so can be used as seed), but subsequent calls from a program provide less randomness (only a few bits).
Code:
DllCall("QueryPerformanceCounter", "Int64P",Cnt)
Back to top
View user's profile Send private message
Laszlo



Joined: 14 Feb 2005
Posts: 4078
Location: Pittsburgh

PostPosted: Tue Jul 03, 2007 8:46 pm    Post subject: Reply with quote

There are three approaches described so far for unique random number generation. The previous post uses a recursive function, which cycles through all possible values. It does not need large memory, very fast, but the randomness is not as good as with the built-in Random command. (The variant, which mixes well the bits of a counter, is an order of magnitude slower.)

The second approach is to generate all the random numbers in advance, and at a request return the next one from the list. It is simple and fast (requiring just one call of a general random number generator for each entry), but uses large memory: several bytes for each value in the range between the smallest and largest requested random numbers, therefore, it is not suitable for very large ranges. 16 million entries (24 bits) represent a practical upper bound with 2GB RAM.

The third approach of unique random number generation uses bookkeeping: keep track of the already generated numbers, and do something at a collision. Bookkeeping can be done with at least three different ways:
-1- Defining dynamic variables (when the internal, highly optimized) variable referencing mechanism of AHK detects collisions). It is the best if the range of the generated random numbers is too large for a bitmap.
-2- Using bitmaps with flags set at the place corresponding to the already generated random numbers
-3- Append to a list the already generated unique random numbers, and search this list for a collision at generating a trial random number. It is the simplest algorithm, but a search in a long list gets slow, making this approach suitable for only a few hundred, maybe a thousand random numbers.

We also have a choice for the action at a collision between unique random numbers generated earlier and a new, trial random number. One possibility is to generate new trial random numbers, until one is found, different from all the ones recorded earlier. A working example (which uses bitmaps as described in -2-) is here. It works pretty fast, until only a few possible numbers remain, when almost all the trial random numbers collide with the recorded ones returned earlier, so the algorithm slows down.

Another strategy at a collision is to check the neighborhood of the colliding number (larger or smaller values), and instead of generating a new trial random number, we return an unused one, close to the value just collided. This can be made much faster by inspecting machine words instead of individual bits, but the randomness is slightly worse. Compiled bitmap functions are available in the Windows NT+ runtime library (ntdll.dll), which further improves the speed:
Code:
DllCall("LoadLibrary", "Str","ntdll.dll")

URandom(_min = "", _max = "") {
   static B, s, sz, min, nc, df
   IfEqual _min,, {
      Random r, 0, df
      r := DllCall("ntdll\RtlFindClearBitsAndSet","UInt",&B,"UInt",1,"UInt",r) ; &BitMap, RunLen, HintIdx -> RunIdx
      If (nc++ = df) { ; no more numbers to return: re-initialize and/or send error message
         nc := 0, VarSetCapacity(s, 4*sz, 0)
         DllCall("ntdll\RtlSetBits","UInt",&B,"UInt",df+1,"UInt",32*sz-df)     ; &BitMap, StartIdx, Num2Set
      }
      Return r + min
   }
   min := _min, df := _max-_min, nc := 0
   sz := ceil((df+1)/32)
   VarSetCapacity(s, 4*sz, 0) ; bytes
   VarSetCapacity(B,8)        ; BitMap Structure: {&buffer, size}
   DllCall("ntdll\RtlInitializeBitMap","UInt",&B,"UInt",&s,"UInt",32*sz) ; size must be a multiple of 32
   DllCall("ntdll\RtlSetBits","UInt",&B,"UInt",df+1,"UInt",32*sz-df)     ; &BitMap, StartIdx, Num2Set
}
(This function re-initializes itself when all the possible random numbers have been returned, but you can, e.g. issue an error message, too.) The search for a clear bit is done by compiled code, so it does not add much to the running time, until the bitmap gets huge. For practical range sizes (up to 100 million entries), the corresponding slowdown is acceptable. For larger ranges the dynamic variable approach is better (-1-, which was described in the first few posts in this thread).

The running time of this function was measured with the following instructions:
Code:
#NoEnv
SetBatchLines -1
Process Priority,,R
; .. ..
t := A_TickCount
URandom(0,n)
Loop %k%
   s := URandom()
MsgBox % A_TickCount-t
Averaging several results gave the following running time values, with n = 500,000 (2GHz Centrino, 1GB RAM, XP SP2)
k = 100000: 815ms
k = 200000: 1625ms
k = 400000: 3275ms
k = 500001: 4198ms
We can see that requesting up to 80% of the possible unique random numbers, the running time is proportional to the number of requests. The last case, which generated all the possible unique random numbers up to 500,000, was only 2% slower than expected.

Increasing n to 5,000,000, k = n+1, one would expect a 10-fold increase in running time. The measured value was 47700ms, some 15% more than 10 times the running time of the 10 times smaller problem. It shows that a quadratic term in the running time expression (corresponding to the search for a clear bit in the bitmap) is not negligible any more. At a further tenfold increase of the range (50 million entries) it will become the dominating term (total ~ 20 minutes), and at a further factor of 10 increase of the range (500 million entries), the running time increases 100 fold, to over 33 hours, with only 62.5 MB memory use. It shows, that the limiting factor of this function is speed, not memory.
Back to top
View user's profile Send private message
Laszlo



Joined: 14 Feb 2005
Posts: 4078
Location: Pittsburgh

PostPosted: Sat Jul 07, 2007 6:47 pm    Post subject: HASH Reply with quote

We cannot use the bitmap approach directly if the range of the random numbers is too large (when the -1- algorithm was recommended). For 32-bit random numbers the memory requirement is 2**32 bits = 512 MB. If your PC has 1GB RAM, or less, this would make some applications to be swapped to disk, causing a large, unpredictable delay. We should restrict the memory use less than 128MB.

AHK applications do not normally process more than 67 million numbers (2**26). If we need large random numbers, we can hash them to 28 bits, and use a 28-bit bitmap to detect collisions among the hash values. The bitmap is never more than 25% full, so collisions are rare. Its size, 2**28 bits = 2**25 Bytes = 32MB, is acceptable. There will be a few random numbers rejected, which happen to have the same hash as a different random number, but a repeated random number is always detected. (Note, that this function should not be used when close to all the random numbers in a range are requested: we just waste time with hashing.)

We can employ the RTL bitmap functions, or only AHK commands, using an optimized version of the corresponding function described above:
Code:
UniqueRandom(_min = "", _max = "") {
   static s, min, nc, df, r
   If _min =
      Loop {
         Random r, 0, df
         If *(&s + (r>>3)) & (1<<(r&7))
            Continue
         NumPut(*(&s + (r>>3)) | (1<<(r&7)), s, r>>3, "UChar")
         If (nc++ = df)
            VarSetCapacity(s, ceil((df+1)/8), nc:=0)
         Return r + min
      }
   min := _min, df := _max-_min, VarSetCapacity(s, ceil((df+1)/8), nc:=0)
}

This function takes 859ms for 100,000 calls in a range of 500,000 (only 5% more than 815ms, the running time of the RTL bitmap version, although closer to generating all the numbers in a range, the RTL version is much better).

We need a hash function, which reduces large integers to 28-bit integers, fast. The simplest solution is the congruential method: take the raw random number modulo 2**28. Unfortunately, it is too regular: two numbers will hash to the same value, if they are k*2**28 apart, so the generated random sequence will never have numbers of that distance. (This is the same with A*x+B mod M, too.) In general, a non-perfect hash function does not degrade the randomness too much, unless it has a too simple structure, but it always detects true collisions. We can make the hash function also random, by choosing its parameters randomly:
Code:
Hash28(x="") {    ; 32-bit int --> 28-bit hash
   Static t0:=00, t1:=65, t2:=10, t3:=97, t4:=50, t5:=69, t6:=12, t7:=21
         ,t8:=60, t9:=46,t10:=84,t11:=62,t12:=66,t13:=13,t14:=79,t15:=37
   IfEqual x,, {  ; Initialize random table
      Loop 15     ; Leave t0 = 0
         Random t%A_Index%, 0, 0x0fffffff ; 28-bit
      ListVars
   }
   i := (x >> 28) & 15
   Return (x ^ t%i%) & 0x0fffffff
}

If larger numbers are to be hashed, we have to use more bits. For 40-bit input the last two commands change to
Code:
   i := (x >> 28) & 15, j := (x >> 32) & 15, k := (x >> 36) & 15,
   Return (x ^ t%i% ^ t%j% ^ t%k%) & 0x0fffffff

One could use three different tables for i, j and k, respectively.

Using a simpler hash function (t%i% = i), here is the resulting unique random number generator:
Code:
URandom28(min0 = "", max0 = "") { ; At most 200M Unique 32-bit random numbers
   static df, min, nc, h, r, s, M := 0x0fffffff
   If min0 =
      Loop {
         Random r, 0, df
         h := (r>>28 ^ r)  &  M  ; 28-bit hash
         If *(&s + (h>>3)) & (1<<(h&7))
            Continue
         NumPut(*(&s + (h>>3)) | (1<<(h&7)), s, h>>3, "UChar")
         If (nc++ = df)
            VarSetCapacity(s, 0x2000000, nc:=0) ; 32MB
         Return r + min
      }
   min := min0, df := max0 - min0
   VarSetCapacity(s, 0x2000000, nc:=0)
}

100,000 calls in a range of 500,000 (or larger) finishes in 1160ms; 200,000 calls in 2275ms (10% faster if we drop the test for exhausted range: 1005/1980ms; it should never happen in this function). It can replace the algorithms based on defining dynamic variables for collision detection (the original approach). This function is slightly slower for few calls, but always uses 32MB RAM, while a function based on the algorithm in the first post uses less memory for few calls and much more memory for many calls.

This method is related to variable name lookup, if it was implemented with hash tables (in the current AHK version it is not), so the function is not that different from the original algorithm. We just left out anything unnecessary in variable lookup and chose a very simple hash function.


The hash idea can also mix with the RTL-bitmap approach (at collision we search for a nearby unused entry). Although, in general, an unused hash does not give directly an unused random number, a pre-image can easily be found for our simple hash function above: h = r>>25 | r & M. We could set the 4 MS bits of r to 0000, or generate a 4-bit random value for them, and also XOR it to the 4 LS bits of h, forming a suitable r. It is rarely needed, so it does not affect the running time much.

If the range is smaller than 2**28, use the original RTL version, otherwise use the following hashing version with fix-sized 32MB bitmap. (In this case all the 256+ million numbers in the range will practically never be generated.)
Code:
URandB32M(min0 = "", max0 = "") { ; Unique random numbers from |Range| >= 2**28
   static B, S, df, min, h, r, M := 0x0fffffff, FindSet := "ntdll\RtlFindClearBitsAndSet"
   IfEqual min0,, {
      Random r, 0, df
      h := (r>>28^r) & M ; 28-bit hash of r
      q := DllCall(FindSet,"UInt",&B,"UInt",1,"UInt",h) ; &BitMap,RunLen,HintIdx->Idx
     ;If (q < 0)... ; no more numbers to return: error (should never happen)
      Return (q = h ? r : q) + min
   }
   min := min0, df := max0 - min0
   VarSetCapacity(S, 0x2000000, 0) ; 32MB
   VarSetCapacity(B,8) ; BitMap Structure: {&buffer, size}
   DllCall("ntdll\RtlInitializeBitMap","UInt",&B,"UInt",&S,"UInt",M+1)
}

The running time relative to the original RTL-bitmap version increases slightly, due to the large bitmap to be set up, and the extra hashing operations. With a range to 0 … 2**29+99, we got the following running time values:
100000: 1080ms (815ms)
200000: 2115ms (1625ms)
400000: 4172ms (3275ms)
500000: 5234ms (4198ms)
5000000: 51625ms (47700ms)
The original running times (for a range of only 500,000) are in parentheses. The slowdown is roughly 30%, except at the last test (8%), because there the original function had to re-initialize 10 times, while the new one never needs that. Just before the re-initialization there are a lot of collisions, which need extra processing.

The RTL bitmap functions provide an advantage (fast search for a free value), when there are lots of collisions. In these settings they are rare. This is why the pure AHK solution above (without overrun test) is slightly faster, so there is no real need for the dll calls.
Back to top
View user's profile Send private message
Laszlo



Joined: 14 Feb 2005
Posts: 4078
Location: Pittsburgh

PostPosted: Sun Jul 08, 2007 11:02 pm    Post subject: One function for all ranges/#calls Reply with quote

If we want to handle all the different parameter ranges and number of calls efficiently, with good randomness and relatively simple code, we can use the RTL-bitmap functions. At initialization we set up the bitmap, maxing its size to 32MB. We always hash, because testing a condition takes almost the same time as the hash, but the code is simpler this way. Hashing does nothing until the range is at most 2**28.

We use the fix point version of the Random command when the range is less than 32 bits, otherwise its floating point version, which provides 53 random bits at each call. This covers practically all of the use cases, and runs at the possible maximum speed. (If max-min contains more than 53 bits, the least significant bits beyond 53 are rounded, but this is almost never noticeable in the generated random numbers.)

The running time is roughly double of that of the linear congruential generator (4 times slower at huge ranges), but the randomness is better. Compared to the algorithm, which randomly permutes the list of all the possible random numbers and returns the next one from this list, the speed is 65% lower, but the memory consumption is 100 times less.
Code:
DllCall("LoadLibrary", "Str","ntdll.dll")
URand(min0 = "", max0 = "") { ; Unique random numbers
   static B, S, d, df, min, h, r, M := 0x0fffffff, FindSet := "ntdll\RtlFindClearBitsAndSet"
   IfEqual min0,, {
      Random r, 0, d          ; Fix- or Floating point random number
      r := r < df ? r : df
      h := (r>>28^r) & M      ; 28-bit hash of r (truncated to integer)
      q := DllCall(FindSet,"UInt",&B,"UInt",1,"UInt",h) ; &BitMap,RunLen,HintIdx->Idx
      Return (q = h ? floor(r) : q) + min ; No more numbers: Return min-1
   }
   min := min0, df := 1 + max0 - min0, d := df > 0x7fffffff ? df+1.0 : df
   sz := ceil( (df > M ? M : df) / 32 )
   VarSetCapacity(S,4*sz,0)   ; <= 32MB
   VarSetCapacity(B,8)        ; BitMap Structure: {&buffer, size}
   DllCall("ntdll\RtlInitializeBitMap","UInt",&B,"UInt",&S,"UInt",32*sz)
   DllCall("ntdll\RtlSetBits","UInt",&B,"UInt",df,"UInt",32*sz>df ? 32*sz-df : 0) ; &BitMap,StartIdx,Num2Set
}

This function returns min-1 if there is no more number different from the previously generated ones, which could be caught by the application to re-initialize the generator or issue an error message.
The running times for n generated numbers:
Range = 0 .. 1<<50|1<<48: n = 100000: 1930ms, n = 200000: 3828ms, n = 500000: 9500ms
Range = 0..500000: n = 100000: 1047ms, n = 200000: 2078ms, n = 500001: 5156±ms
Range =100000..200000: n = 100001: 1016±ms, n = 70000: 719ms.
(Here "±" indicates excessive variations, due to the large, unpredictable number of collisions among generated random numbers.)
Back to top
View user's profile Send private message
rani



Joined: 18 Mar 2008
Posts: 95

PostPosted: Mon Jul 14, 2008 6:29 pm    Post subject: bitmap set/get in offset Reply with quote

ntdll bitmap usage

hi Lazslo,

1.
what is the syntax to set/get bit 1 or 0 in specific location ?
2.
what is the max number (offset or location) we can set to some Var ,
that represent the bitmap number ?
3.
how can we define tcount of 1's or 0's ?
4.
how can we iterate on all 1's or all 0's one after another

any code syntax will apprciated
Back to top
View user's profile Send private message
Laszlo



Joined: 14 Feb 2005
Posts: 4078
Location: Pittsburgh

PostPosted: Mon Jul 14, 2008 9:56 pm    Post subject: Reply with quote

I don't remember the details. These are the RTL bitmap functions exported:
Code:
RtlInitializeBitMap  ; &BitMap, &Buffer, Size (k*32)
RtlSetBits           ; &BitMap, StartIdx, Num2Set
RtlClearBits         ; &BitMap, StartIdx, Num2Set
RtlNumberOfSetBits   ; &BitMap
RtlNumberOfClearBits ; &BitMap
RtlFindSetBits       ; &BitMap, RunLen, HintIdx  -> RunIdx
RtlFindClearBits     ; &BitMap, RunLen, HintIdx  -> RunIdx
RtlAreBitsSet        ; &BitMap, StartIdx, Length -> Boolean
RtlAreBitsClear      ; &BitMap, StartIdx, Length- > Boolean
RtlSetAllBits        ; &BitMap
RtlClearAllBits      ; &BitMap
Their descriptions: http://msdn.microsoft.com/en-us/library/ms802981.aspx
Back to top
View user's profile Send private message
rani



Joined: 18 Mar 2008
Posts: 95

PostPosted: Tue Jul 15, 2008 4:28 am    Post subject: Reply with quote

Hi Laszlo

1.
thanks for info
these are the functions I need

pls note I completely, don't know the syntax how to run, by AHK
these functions, even I look on the code demo's in your forum page.
the code syntax and it's params looks strange for me.

can you send some sample code, in AHK, how to run the bitmap functions ?

2. from your answer, the max bitmap var is 32k ?
what is the limit offset I can set/get ? or it is unlimited ?
if I need more range how I can handle it ?

rgrds
Ell
Back to top
View user's profile Send private message
Laszlo



Joined: 14 Feb 2005
Posts: 4078
Location: Pittsburgh

PostPosted: Tue Jul 15, 2008 3:01 pm    Post subject: Reply with quote

ell wrote:
some sample code, in AHK, how to run the bitmap functions
Code:
; -- shorthands --
InitBitMap= ntdll\RtlInitializeBitMap  ; &BitMap, &Buffer, Size (k*32)
SetBits   = ntdll\RtlSetBits           ; &BitMap, StartIdx, Num2Set
ClearBits = ntdll\RtlClearBits         ; &BitMap, StartIdx, Num2Set
#SetBits  = ntdll\RtlNumberOfSetBits   ; &BitMap
#ClearBits= ntdll\RtlNumberOfClearBits ; &BitMap
@SetBits  = ntdll\RtlFindSetBits       ; &BitMap, RunLen, HintIdx  -> RunIdx
@ClearBits= ntdll\RtlFindClearBits     ; &BitMap, RunLen, HintIdx  -> RunIdx
?SetBits  = ntdll\RtlAreBitsSet        ; &BitMap, StartIdx, Length -> Boolean
?ClearBits= ntdll\RtlAreBitsClear      ; &BitMap, StartIdx, Length- > Boolean
SetAll    = ntdll\RtlSetAllBits        ; &BitMap
ClearAll  = ntdll\RtlClearAllBits      ; &BitMap

FillMem32 = ntdll\RtlFillMemoryUlong   ; &Mem, 4, 32bitValue
LS64 =ntdll\RtlFindLeastSignificantBit ; 64bitValue -> BitIdx
MS64 =ntdll\RtlFindMostSignificantBit  ; 64bitValue -> BitIdx


VarSetCapacity(B, 8, 0)                ; Bitmap header always 8 bytes
VarSetCapacity(y, 16777216, 0)         ; 16MB = 128 Mbit BitMap buffer

DllCall(InitBitMap, UInt,&B, UInt,&y, UInt,64)             ; 64 bits in the bitmap (<= 134217728)
DllCall(FillMem32, UInt,&y, UInt,4, Uint,11)               ; write data 1101 directly into buffer

MsgBox % DllCall(#SetBits,  UInt,&B)                       ;  3 bits are set
MsgBox % DllCall(#ClearBits,UInt,&B)                       ; 61 bits are clear
MsgBox % DllCall(?SetBits, UInt,&B, UInt,0, UInt,1, UChar) ; 1 = LS bit
MsgBox % DllCall(?SetBits, UInt,&B, UInt,1, UInt,1, UChar) ; 1 = bit 1
MsgBox % DllCall(?SetBits, UInt,&B, UInt,2, UInt,1, UChar) ; 0 = bit 2
MsgBox % DllCall(?SetBits, UInt,&B, UInt,3, UInt,1, UChar) ; 1 = bit 3

DllCall(SetBits, UInt,&B, Uint,9, Uint,2)                  ; Set bits 9 and 10

MsgBox % DllCall(?SetBits, UInt,&B, UInt,8, UInt,1, UChar) ; 0 = bit 8
MsgBox % DllCall(?SetBits, UInt,&B, UInt,9, UInt,1, UChar) ; 1 = bit 9

DllCall(ClearBits, UInt,&B, Uint,9, Uint,1)                ; Clear bit 9

MsgBox % DllCall(?SetBits, UInt,&B, UInt,9, UInt,1, UChar) ; 0 = bit 9 now
MsgBox % DllCall(@SetBits, UInt,&B, UInt,1, UInt,4)        ; 10 = next set bit above bit4

ell wrote:
the max bitmap var is 32k ?
No, there is no limit, other than what Windows can allocate. Hundreds of MB's are OK, but you have to set a maximum size. As in the example, 128 million bits only add 16MB memory use.

Edit: Fixed a bug
Edit: added more examples


Last edited by Laszlo on Tue Jul 15, 2008 9:46 pm; edited 2 times in total
Back to top
View user's profile Send private message
rani



Joined: 18 Mar 2008
Posts: 95

PostPosted: Tue Jul 15, 2008 4:09 pm    Post subject: Reply with quote

Hi Laszlo

thank again for info,

1.
if I understand your calculations:
I can set a bit and then get a bit in locations ,let say
in position 128MB (or little less) means:
posBit :=128,000,000
DllCall(SetBis, UInt,&y, UInt,posBit, UInt,1, UChar) ; set bit 1 in position 128mb
and if I set 0 bit to this position I do:
DllCall(ClearBits, UInt,&y, UInt,posBit, UInt,0, UChar) ; set bit 0 in position 128mb

is it correct ?

2. how I iterate on only bits=1 in the var capacity ?,
do I have to Iterate on all the var capacity ?
or there is a function : get next bit 1 from position let say 1001 and so on ...
otherwise to run each time on all var should take long time ?
is there some techinque to run on next bit=1 position ?
is the iterating on bits working fast ?

3. there some functions you define, I don't understand what they mean like:fillmem32,LS64,MS64
all others I understand

I go to test the speed of bit set's/get's


rgrds
Ell
Back to top
View user's profile Send private message
Laszlo



Joined: 14 Feb 2005
Posts: 4078
Location: Pittsburgh

PostPosted: Tue Jul 15, 2008 6:45 pm    Post subject: Reply with quote

- You reference the bitmap with its header, set up in the beginning with RtlInitializeBitMap.
- You can read or write the buffer directly, if you want to
- RtlFindSetBits or RtlFindClearBits will return bit positions, without the need to examine every bit. If you set the HintIndex parameter one larger than what you have processed, the next call will give you the position of the next desired bit, really fast.
- FillMem32 writes the third parameter (UInt32) to the address specified by the 1st parameter. Parameter2 must be 4.
- LS64 returns the position of the least significant bit, which is set (=1) in a 64-bit integer.
- MS64 returns the position of the most significant bit, which is set (=1) in a 64-bit integer.
Back to top
View user's profile Send private message
rani



Joined: 18 Mar 2008
Posts: 95

PostPosted: Tue Jul 15, 2008 9:11 pm    Post subject: Reply with quote

Hi Laszlo,

RtlFindSetBits
in what param I set the RunLen, HintIdx ?
can you send also the syntax of :
RtlFindSetBits
and
RtlFindClearBits
where exactly I put HintIdx (find next from hintIdx) in dllCall ?

I think you are hight level mathematician ?

rgrds
Ell
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions All times are GMT
Goto page Previous  1, 2, 3, 4, 5, 6  Next
Page 4 of 6

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group