CLOSE

Recommanded Free YOUTUBE Lecture: <% selectedImage %>

### 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))
}
}

```

성능은 아래와 같다. getRandom(100)을 10000회 실행한 결과다. ### 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 일반

컴퓨터는 입력에 따라 출력이 정해진다. 따라서 컴퓨터는 의사난수만을 만들 수 있다. 이 입력 값을 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 예제다.
```/*
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) {
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 := 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)
}

```