[Java] 자바 랜덤함수(Random) 및 Seed 설명

    자바(Java) 언어는 컴퓨터에게 랜덤값을 뿌리는 2가지 방법(설치시 내장)을 제공해주고 있다. 하나는 java.lang.math 클래스의 메소드인 random 함수가 있고, java.util.random 클래스에서 사용하는 랜덤함수가 있다. 여기서는 랜덤에서 꼭 알아야 될 Seed에 대한 설명과, 가장 많이 활용되고 있는 java.util.random 클래스를 이용법을 정리해보고자 한다.

     

    자바(Java) Random함수와 Seed 설명

     

    Seed에 대해

    컴퓨터는 기본적으로 난수를 생성할 수 없다. 사람이란 존재는 현재 수많은 상황에 놓여져 있고, 매번 유사한 상황이 된다 하더라도 다른 선택을 할 수 있다 바로 소울(soul)이란 존재를 갖고 있기 때문이다. 그러나 컴퓨터는 인간과 같은 상황에 놓이기 힘들고, 동일한 상태값이 들어오면 동일한 값을 출력하게 되며, 이를 결정적 유한 오토마타(Deterministic Finite Automata)라고 한다.

     

    그렇기 때문에 난수값을 생성하기 위해서는 몇가지 상황이라는 시드(Seed)를 생성하여 그 시드에 맞는 난수를 결정하게 된다. 즉 컴퓨터가 만들어내는 난수는 Seed에 따라 동일하게 나오는 해쉬(Hash)값과 유사하다는 것이다. 이 난제를 해결하기 위해 랜덤을 제공하는 기능들은 Seed값을 입력하지 않으면 보통 컴퓨터의 현재 시간(current time)을 seed값으로 선택하고 난수값을 생성하여 사람들은 컴퓨터가 매우 영특하게 난수값을 생성한다고 착각하게 된다.

     

    Java.util.random 클래스

    대표적으로 가장 많이 사용되는 랜덤함수를 꼽자면 바로 java.util.random 클래스일 것이다. java.util은 별도로 설치할 필요 없이 java를 설치하면 모두 사용할 수 있기 때문에 import만 하면 사용이 가능하다.

     

    0부터 9사이의 숫자값을 5번 랜덤으로 호출하기

    package test;
    
    import java.util.Random;
    
    public class main {
    	public static void main(String[] args) {
    		Random rnd = new Random();
    		
    		for(int i = 0; i < 5; i++) {
    			System.out.println(rnd.nextInt(10));
    		}
    	}	
    }

    사용방법은 위와 같이 Random 클래스를 생성하고, rnd.nextInt와 같은 원하는 타입의 형을 호출하면 된다. 값은 0부터 시작하기 때문에 10의 값을 넣었다면, 0부터 9까지의 값을 호출하게 되며, 1부터 10까지의 값을 호출하고 싶으면

     

    package test;
    
    import java.util.Random;
    
    public class main {
    	public static void main(String[] args) {
    		Random rnd = new Random();
    		
    		for(int i = 0; i < 5; i++) {
    			System.out.println(rnd.nextInt(10)+1);
    		}
    	}	
    }

    이와같이, rnd.nextInt(10)+1처럼 마지막에 값을 더해주면 된다. 위 예제는 매번 실행할 때마다 달라지게 되는데 이유는 Random객체 안에 별도로 선언하지 않으면 현재의 시간값을 인자로 난수 테이블을 생성하기 때문이다. 일단 위 내용(2번째 예제)을 2번 연속으로 실행했을 때의 결과는

     

    첫번째 결과

    1
    6
    10
    2
    1
    

     

    두번째 결과

    1
    3
    2
    8
    3
    

    이와 같이 결과가 서로 다르다는 것을 알 수 있다. 즉, 컴퓨터의 난수 시스템이 우리가 원하는 형태로 작동이 된다는 것이다. 

     

    Random 생성자

    /**
         * Creates a new random number generator. This constructor sets
         * the seed of the random number generator to a value very likely
         * to be distinct from any other invocation of this constructor.
         */
        public Random() {
            this(seedUniquifier() ^ System.nanoTime());
        }
        
        /**
         * Creates a new random number generator using a single {@code long} seed.
         * The seed is the initial value of the internal state of the pseudorandom
         * number generator which is maintained by method {@link #next}.
         *
         * <p>The invocation {@code new Random(seed)} is equivalent to:
         *  <pre> {@code
         * Random rnd = new Random();
         * rnd.setSeed(seed);}</pre>
         *
         * @param seed the initial seed
         * @see   #setSeed(long)
         */
        public Random(long seed) {
            if (getClass() == Random.class)
                this.seed = new AtomicLong(initialScramble(seed));
            else {
                // subclass might have overridden setSeed
                this.seed = new AtomicLong();
                setSeed(seed);
            }
        }

    위의 설명이 올바른지 확인하기 위해서 랜덤 클래스의 생성자를 확인해보면, System.nanoTime(); 이라는 값을 사용하는 것을 알 수 있으며, Random에 long 인자값을 넣으면 인자값으로 setSeed를 호출하는 것을 알 수 있다. 그럼 Seed를 고정하면 동일한 결과를 내보내는지 확인해보도록 한다.

     

     

    랜덤 생성자에 10입력

    package test;
    
    import java.util.Random;
    
    public class main {
    	public static void main(String[] args) {
    		Random rnd = new Random(10);	// 시드값에 10을 고정
    		
    		for(int i = 0; i < 5; i++) {
    			System.out.println(rnd.nextInt(10)+1);
    		}
    	}	
    }

    결과

    4
    1
    4
    1
    7

     

    위 10값을 입력한 후, 몇번을 호출하더라도 동일한 41417이라는 결과를 반복한다. 즉 컴퓨터의 난수는 진정한 난수가 아니라 이미 만들어진 난수 테이블에 값을 가져오는 느낌이라 생각하면 맞을 것이다. 

     

    시드값 변경하기

    시드값을 변경하기 위해서는 심플하게 new Random을 다시 하거나, setSeed 메소드를 이용하는 방법이 있다(위 Random 클래스의 생성자에서 사용했던 방식) new Random에 시드값을 입력하였을 때 해당 생성자에서 곧장 setSeed를 호출하는 것을 볼 수 있는데 그 메소드를 직접 핸들링 하는 방식이다.

     

    package test;
    
    import java.util.Random;
    
    public class main {
    	public static void main(String[] args) {
    		Random rnd = new Random();
    		rnd.setSeed(System.currentTimeMillis());
    		
    		for(int i = 0; i < 5; i++) {
    			System.out.print(rnd.nextInt(10)+1 + " ");
    		}
    	}
    }

     

    결과

    10 2 8 9 9 

     

    nextDouble 형을 사용하는 방법

    nextInt처럼 nextDouble등의 다양한 값들도 제공해준다. 그냥 nextDouble 값을 호출한다면 다음처럼 0부터 1사이의 실수값을 리턴한다. 

     

    nextInt와 달리 인자값을 받지 않는 nextDouble 메소드

    nextDouble 메소드 안에 혹시나 double값을 저장하면 되는거 아닌가? 생각할 수 있는데 위와 같이 Random 클래스안에는 인자값을 제공하는 것은 nextInt만 제공이 된다. 그렇다면 왜 제공이 안되는 것인가? 조금만 생각하면 알 수 있다.

     

    바로 실수값 자체가 무한의 수를 표현할 수 있기 때문이다. 정수는 1과 3사이의 값은 2밖에 표현해내지 못하지만 실수형은 무한의 수를 표현할 수 있다. 그렇기에 이를 이용해서 nextInt에 정수값을 줘서 값의 범위를 지정하는 효과를 흉내낼 수 있는 것이다.

     

    우선 비교를 위해 rnd.nextDouble을 해본다. 동일한 효과를 얻기 위해서 Seed값을 10으로 고정하였다.

     

    package test;
    
    import java.util.Random;
    
    public class main {
    	public static void main(String[] args) {
    		Random rnd = new Random(10);
    		
    		for(int i = 0; i < 5; i++) {
    			System.out.println(rnd.nextDouble() + " ");
    		}
    	}
    }

     

    결과

    0.7304302967434272 
    0.2578027905957804 
    0.059201965811244595 
    0.24411725056425315 
    0.8188090228552316 

     

    위 예제에서 rnd.nextDouble 부분을 아래와 같이 수정한다.

    System.out.print((int)(rnd.nextDouble()*10)+1 + " ");

    실수값에 10을 곱하면, 소수점자리가 왼쪽으로 shift하여 7.304..., 2.578.. 처럼 값이 변하게 된다. 여기에 int형으로 변환한 후 + 1을 주는 것이다. int형으로 변환하게 되면 소수점이 절삭되기 때문에 7+1, 2+1 인 8,3... 의 결과 들이 나오게 된다. 

     

    예제 결과

    8 3 1 3 9 

     

    연관자료

    2020/11/26 - [Language/Java] - [Java] 자바로 로또 번호 생성하기
    반응형

    댓글

    Designed by JB FACTORY