diff --git a/util/grand/grand.go b/util/grand/grand.go index c62413359..10fab14ab 100644 --- a/util/grand/grand.go +++ b/util/grand/grand.go @@ -8,6 +8,7 @@ package grand import ( + "encoding/binary" "unsafe" ) @@ -28,6 +29,23 @@ func MeetProb(prob float32) bool { return Intn(1e7) < int(prob*1e7) } +// B retrieves and returns random bytes of given length . +func B(n int) []byte { + if n <= 0 { + return nil + } + i := 0 + b := make([]byte, n) + for { + copy(b[i:], <-bufferChan) + i += 4 + if i >= n { + break + } + } + return b +} + // N returns a random int between min and max: [min, max]. // The and also support negative numbers. func N(min, max int) int { @@ -115,3 +133,20 @@ func Perm(n int) []int { } return m } + +// Intn returns a int number which is between 0 and max: [0, max). +// +// Note that: +// 1. The can only be greater than 0, or else it returns directly; +// 2. The result is greater than or equal to 0, but less than ; +// 3. The result number is 32bit and less than math.MaxUint32. +func Intn(max int) int { + if max <= 0 { + return max + } + n := int(binary.LittleEndian.Uint32(<-bufferChan)) % max + if (max > 0 && n < 0) || (max < 0 && n > 0) { + return -n + } + return n +} diff --git a/util/grand/grand_buffer.go b/util/grand/grand_buffer.go new file mode 100644 index 000000000..fc871742a --- /dev/null +++ b/util/grand/grand_buffer.go @@ -0,0 +1,62 @@ +// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package grand + +import ( + "crypto/rand" +) + +const ( + // Buffer size for uint32 random number. + gBUFFER_SIZE = 10000 +) + +var ( + // bufferChan is the buffer for random bytes, + // every item storing 4 bytes. + bufferChan = make(chan []byte, gBUFFER_SIZE) +) + +func init() { + go asyncProducingRandomBufferBytesLoop() +} + +// asyncProducingRandomBufferBytes is a named goroutine, which uses a asynchronous goroutine +// to produce the random bytes, and a buffer chan to store the random bytes. +// So it has high performance to generate random numbers. +func asyncProducingRandomBufferBytesLoop() { + var ( + step = 0 + buffer = make([]byte, 1024) + ) + for { + if n, err := rand.Read(buffer); err != nil { + panic(err) + } else { + for i := 0; i < n-4; { + bufferChan <- buffer[i : i+4] + i++ + } + // Reuse the rand buffer. + for i := 0; i < n; i++ { + step = int(buffer[0]) % 10 + if step != 0 { + break + } + } + // The step cannot be 0, + // as it will produce the same random number as previous. + if step == 0 { + step = 2 + } + for i := 0; i < n-4; { + bufferChan <- buffer[i : i+4] + i += step + } + } + } +} diff --git a/util/grand/grand_intn.go b/util/grand/grand_intn.go deleted file mode 100644 index 12bad78eb..000000000 --- a/util/grand/grand_intn.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. -// -// This Source Code Form is subject to the terms of the MIT License. -// If a copy of the MIT was not distributed with this file, -// You can obtain one at https://github.com/gogf/gf. - -package grand - -import ( - "crypto/rand" - "encoding/binary" -) - -const ( - // Buffer size for uint32 random number. - gBUFFER_SIZE = 10000 -) - -var ( - // Buffer chan. - bufferChan = make(chan uint32, gBUFFER_SIZE) -) - -// It uses a asynchronous goroutine to produce the random number, -// and a buffer chan to store the random numbers. -// So it has high performance to generate random numbers. -func init() { - var ( - step = 0 - buffer = make([]byte, 1024) - ) - go func() { - for { - if n, err := rand.Read(buffer); err != nil { - panic(err) - } else { - for i := 0; i < n-4; { - bufferChan <- binary.LittleEndian.Uint32(buffer[i : i+4]) - i++ - } - // Reuse the rand buffer. - for i := 0; i < n; i++ { - step = int(buffer[0]) % 10 - if step != 0 { - break - } - } - // The step cannot be 0, - // as it will produce the same random number as previous. - if step == 0 { - step = 2 - } - for i := 0; i < n-4; { - bufferChan <- binary.BigEndian.Uint32(buffer[i : i+4]) - i += step - } - } - } - }() -} - -// Intn returns a int number which is between 0 and max: [0, max). -// -// Note that: -// 1. The can only be greater than 0, or else it returns directly; -// 2. The result is greater than or equal to 0, but less than ; -// 3. The result number is 32bit and less than math.MaxUint32. -func Intn(max int) int { - if max <= 0 { - return max - } - n := int(<-bufferChan) % max - if (max > 0 && n < 0) || (max < 0 && n > 0) { - return -n - } - return n -} diff --git a/util/grand/grand_z_bench_test.go b/util/grand/grand_z_bench_test.go index 7790ba982..b659a8dfe 100644 --- a/util/grand/grand_z_bench_test.go +++ b/util/grand/grand_z_bench_test.go @@ -60,6 +60,12 @@ func Benchmark_StrSymbols(b *testing.B) { } } +func Benchmark_Uint32Converting(b *testing.B) { + for i := 0; i < b.N; i++ { + binary.LittleEndian.Uint32([]byte{1, 1, 1, 1}) + } +} + func Benchmark_Buffer(b *testing.B) { for i := 0; i < b.N; i++ { if _, err := rand.Read(buffer); err == nil { diff --git a/util/grand/grand_z_unit_test.go b/util/grand/grand_z_unit_test.go index 0be667eea..d58a77b06 100644 --- a/util/grand/grand_z_unit_test.go +++ b/util/grand/grand_z_unit_test.go @@ -90,15 +90,7 @@ func Test_Rand(t *testing.T) { }) } -func Test_Str(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - for i := 0; i < 100; i++ { - t.Assert(len(grand.S(5)), 5) - } - }) -} - -func Test_RandS(t *testing.T) { +func Test_S(t *testing.T) { gtest.C(t, func(t *gtest.T) { for i := 0; i < 100; i++ { t.Assert(len(grand.S(5)), 5) @@ -111,6 +103,24 @@ func Test_RandS(t *testing.T) { }) } +func Test_B(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + for i := 0; i < 100; i++ { + b := grand.B(5) + t.Assert(len(b), 5) + t.AssertNE(b, make([]byte, 5)) + } + }) +} + +func Test_Str(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + for i := 0; i < 100; i++ { + t.Assert(len(grand.S(5)), 5) + } + }) +} + func Test_RandStr(t *testing.T) { str := "我爱GoFrame" gtest.C(t, func(t *gtest.T) {