package main
import (
"fmt"
"math/rand"
)
type jump struct {
max int64
count int32
loop int64
}
func (self jump) getRandom(key int32) int32 {
var rbuff int32
for i := 0; i < 30; i++ {
r := self.getZeroOrOne()
rbuff = rbuff << 1
if r == 1 {
rbuff = rbuff | 1
}
}
return rbuff % key
}
func (self *jump) getZeroOrOne() int64 {
rtv := rand.Int63() % 2
return rtv
}
func main() {
rand := jump{max: 10}
for i := 0; i < 10; i++ {
fmt.Println(rand.getRandom(10))
}
}
Random 일반
컴퓨터는 입력에 따라 출력이 정해진다. 따라서 컴퓨터는 의사난수만을 만들 수 있다. 이 입력 값을 Seed라고 한다. Seed 값에 따라서 난수표가 달라진다. Seed를 이용하는 난수 발생기는 Seed를 예측 할 수 있기 때문에, 시간을 이용해서 Seed를 예측하기 힘들게 한다. 보통은 밀리초 단위로 설정하기 때문에 인간 레벨에서 같은 seed를 찾는 건 불가능에 가깝다.
시간을 이용해서 Seed를 예측하기 힘들게 할 수는 있지만, 만들어진 대략적인 시간을 안다면 컴퓨터를 이용해서 계산을 할 수 있기 때문에 안전한 방법은 아니다. 리눅스는 /dev/random과 /dev/urandom 디바이스로 해결하고 있다. 이 디바이스는 Hardware random generator로 IO 디바이스 드라이버에서 발생하는 입력 신호의 노이즈를 모은 정보를 이용해서 난수를 만든다. 이건 컴퓨터를 이용한다고 하더라도 현실적으로 예측 할 수 없기 때문에 매우 안전하게 사용 할 수 있다.
/dev/random과 /dev/urandom의 차이는 노이즈 수집방식에 있다. /dev/random은 노이즈가 충분히 수집되기 전에는 난수를 발생하지 않는다. 즉 블럭킹된다. 실제 난수 입력을 요청하는 몇몇 애플리케이션들은 빠르게 난수를 만들기 위해서 마우스나 키보드 입력을 요구하기도 한다. /dev/urandom은 노이즈가 충분히 수집되지 않아도 블럭킹하지 않고 난수를 만들 수 있다.
아래 예제 코드는 math/rand와 /dev/urandom 예제다.
Jump 기반
package main import ( "fmt" ) type jump struct { max int64 j int64 b int64 key uint64 } func (self jump) getRandom(key uint64) int32 { self.max = self.max - 1 if self.max <= 0 { self.max = 1 } self.key = key for self.getZeroOrOne() == 1 { } return int32(self.b) } func (self *jump) getZeroOrOne() int64 { if self.j > self.max { return 0 } self.b = self.j self.key = self.key*2862933555777941757 + 1 self.j = int64(float64(self.b+1) * (float64(int64(1)<<31) / float64((self.key>>33)+1))) return 1 } func main() { var i uint64 rand := jump{max: 10} for i = 0; i < 10; i++ { fmt.Println(rand.getRandom(i)) } }Rand Mod 기반
package main import ( "fmt" "math/rand" ) type jump struct { max int64 count int32 loop int64 } func (self jump) getRandom(key int32) int32 { var rbuff int32 for i := 0; i < 30; i++ { r := self.getZeroOrOne() rbuff = rbuff << 1 if r == 1 { rbuff = rbuff | 1 } } return rbuff % key } func (self *jump) getZeroOrOne() int64 { rtv := rand.Int63() % 2 return rtv } func main() { rand := jump{max: 10} for i := 0; i < 10; i++ { fmt.Println(rand.getRandom(10)) } }Random 일반
/* 0 ~ (max - 1) 범위에서 랜덤 값을 출력한다. math/rand와 crypto/rand 두 개 패키지에서 제공하는 랜덤함수를 사용하고 있다. crypto/rand는 /dev/urandom 커널 디바이스를 기반으로 랜덤값을 출력한다. 두 개 패키지의 랜덤함수를 하나의 main 함수에서 테스트 하기 위해서 interface 를 이용했다. */ package main import ( "crypto/rand" "encoding/binary" "errors" "fmt" prand "math/rand" "os" "time" ) var ( ZeroBoundaryError = errors.New("Can't mod by zero") ) type Random interface { getZeroOrOne() (uint32, error) getRandom(uint32) (uint32, error) printType() } type uRand struct { randBuff []byte } func (u uRand) printType() { fmt.Println("/dev/urandom ------") } func (u uRand) getZeroOrOne() (uint32, error) { _, err := rand.Read(u.randBuff) if err != nil { return 0, err } return binary.LittleEndian.Uint32(u.randBuff), nil } func (u *uRand) getRandom(max uint32) (uint32, error) { if u.randBuff == nil { fmt.Println("Rand Init") u.randBuff = make([]byte, 4) } if max == 0 { return 0, ZeroBoundaryError } num, err := u.getZeroOrOne() if err != nil { return 0, err } return num % max, nil } type mRand struct { source *prand.Rand } func (m mRand) printType() { fmt.Println("Go Random function ------") } func (m mRand) getZeroOrOne() (uint32, error) { return m.source.Uint32(), nil } func (m *mRand) getRandom(max uint32) (uint32, error) { if m.source == nil { fmt.Println("Rand Init") seed := time.Now().Unix() s2 := prand.NewSource(seed) m.source = prand.New(s2) } num, err := m.getZeroOrOne() if err != nil { return 0, err } return num % max, nil } func main() { randomEngine := [2]Random{&uRand{}, &mRand{}} for _, engine := range randomEngine { engine.printType() for i := 0; i < 10; i++ { val, err := engine.getRandom(10000) if err != nil { fmt.Println(err.Error()) os.Exit(1) } fmt.Println(val) } } os.Exit(0) }참고
Recent Posts
Archive Posts
Tags