[java] 이 임의의 값에 50/50 대신 25/75 분포가있는 이유는 무엇입니까?

편집 : 기본적으로 내가 작성하려고하는 것은 1 비트 해시입니다 double.

doubletrue또는 false50/50 기회 를 매핑하고 싶습니다 . 이를 위해 임의의 숫자를 선택하는 코드를 작성 했습니다 (예를 들어, 규칙이있는 데이터에 이것을 사용하고 여전히 50/50 결과를 얻고 싶습니다) . 마지막 비트를 확인하고 y1인지 아니면 증가 하는지 n확인하십시오. 0.

그러나이 코드는 지속적으로 25 % y및 75 % n입니다. 왜 50/50이 아닌가? 왜 그렇게 이상하지만 솔직한 (1/3) 분포입니까?

public class DoubleToBoolean {
    @Test
    public void test() {

        int y = 0;
        int n = 0;
        Random r = new Random();
        for (int i = 0; i < 1000000; i++) {
            double randomValue = r.nextDouble();
            long lastBit = Double.doubleToLongBits(randomValue) & 1;
            if (lastBit == 1) {
                y++;
            } else {
                n++;
            }
        }
        System.out.println(y + " " + n);
    }
}

출력 예 :

250167 749833



답변

nextDouble은 다음과 같이 작동하기 때문에 : ( source )

public double nextDouble()
{
    return (((long) next(26) << 27) + next(27)) / (double) (1L << 53);
}

next(x)x임의의 비트를 만듭니다 .

왜 이것이 중요한가? 첫 번째 부분 (나눗셈 이전)에 의해 생성 된 숫자의 약 절반이보다 작기 1L << 52때문에 그 의미는 채울 수있는 53 비트를 완전히 채우지 못하므로 의미의 최하위 비트는 항상 0입니다.


많은 관심을 받고 있기 때문에 doubleJava (및 다른 많은 언어)의 실제 모습 과이 질문에서 왜 중요한지에 대한 추가 설명 이 있습니다.

기본적으로 double다음과 같습니다 : ( source )

이중 레이아웃

이 그림에서 보이지 않는 매우 중요한 세부 사항은 숫자가 “정규화”되었다는 것입니다. 1 이므로 53 비트 분수는 1로 시작하고 (그런 지수를 선택함으로써) 1은 생략됩니다. 그렇기 때문에 그림에 분수 (유의)에 대해 52 비트가 표시되지만 실제로 53 비트가 있습니다.

정규화는 코드에서 nextDouble 는 53 비트 설정되면 해당 비트는 암시 적 선행 1이며 사라지고 나머지 52 비트는 문자 그대로 결과의 의미에 복사됨을 의미합니다 double. 그러나 해당 비트가 설정되지 않은 경우 나머지 비트는 설정 될 때까지 왼쪽으로 이동해야합니다.

평균적으로 생성 된 숫자의 절반이 유의 한 경우에 속합니다. 전혀 왼쪽으로 이동 하지 않은 (약 절반은 0을 최하위 비트로 사용)이고 나머지 절반은 1 이상 (또는 완전히 0) 따라서 최하위 비트는 항상 0입니다.

1 : 항상, 항상 그런 것은 아닙니다. 가장 높은 숫자는 0이 아닙니다.이 숫자는 비정규 또는 비정규 숫자라고합니다 ( wikipedia : denormal number 참조) .


답변

로부터 문서 :

nextDouble 메소드는 다음과 같이 Random 클래스에 의해 구현됩니다.

public double nextDouble() {
  return (((long)next(26) << 27) + next(27))
      / (double)(1L << 53);
}

그러나 그것은 또한 다음을 강조합니다 (강조 광산).

[이전 버전의 Java에서는 결과가 다음과 같이 잘못 계산되었습니다.

 return (((long)next(27) << 27) + next(27))
     / (double)(1L << 54);

이것은 더 나은 것은 아니지만 동등하게 보일지 모르지만 실제로는 부동 소수점 숫자의 반올림으로 인해 큰 불균일성이 발생했습니다. 유효성의 하위 비트가 0 일 가능성의 세 배였습니다 그것보다 1이 될 것입니다 ! 이 불균일성은 실제로는 그다지 중요하지 않지만 완벽을 위해 노력합니다.]

이 메모는 Java 5 이후로 존재했습니다 (Java <= 1.4의 문서는 loginwall 뒤에 있으며 확인하기에는 너무 게으름). Java 8에서도 문제가 여전히 존재하기 때문에 이것은 흥미 롭습니다. 아마도 “고정 된”버전은 테스트되지 않았습니까?


답변

부동 소수점 숫자가 표현되는 방식을 고려할 때이 결과는 놀랍지 않습니다. 4 비트의 정밀도로 매우 짧은 부동 소수점 유형이 있다고 가정 해 봅시다. 균일하게 분포 된 0과 1 사이의 난수를 생성하는 경우 16 가지 가능한 값이 있습니다.

0.0000
0.0001
0.0010
0.0011
0.0100
...
0.1110
0.1111

그것이 기계에서 보이는 방식이라면, 하위 비트를 테스트하여 50/50 분포를 얻을 수 있습니다. 그러나 IEEE float는 가수의 2 배의 힘으로 표현됩니다. 플로트의 한 필드는 2의 거듭 제곱입니다 (고정 오프셋). 2의 거듭 제곱은 “mantissa”부분이 항상> = 1.0 및 <2.0이되도록 선택됩니다. 이것은 사실상 다음과 같은 숫자 이외의 숫자를 0.0000나타냅니다.

0.0001 = 2^(-4) x 1.000
0.0010 = 2^(-3) x 1.000
0.0011 = 2^(-3) x 1.100
0.0100 = 2^(-2) x 1.000
...
0.0111 = 2^(-2) x 1.110
0.1000 = 2^(-1) x 1.000
0.1001 = 2^(-1) x 1.001
...
0.1110 = 2^(-1) x 1.110
0.1111 = 2^(-1) x 1.111

( 1이진 점 앞의 값은 묵시적인 값입니다. 32 비트 및 64 비트 부동 소수점의 경우 실제로 이것을 보유하기 위해 비트가 할당되지 않습니다 1.)

그러나 위의 내용을 보면 왜 표현을 비트로 변환하고 로우 비트를 보면 시간의 75 %가 0이되는 이유를 알 수 있습니다. 이는 0.5 (이진 0.1000) 미만의 모든 값으로 , 가능한 값의 절반이며 가수가 이동하여 하위 비트에 0이 표시됩니다. 가수가 암시 적으로 1을 포함하지 않고 52 비트를 가질 때 상황은 본질적으로 동일하다 double.

실제로 @sneftel이 의견에서 제안한 것처럼 다음을 생성하여 분포에 16 개 이상의 가능한 값을 포함 할 있습니다.

0.0001000 with probability 1/128
0.0001001 with probability 1/128
...
0.0001111 with probability 1/128
0.001000  with probability 1/64
0.001001  with probability 1/64
...
0.01111   with probability 1/32
0.1000    with probability 1/16
0.1001    with probability 1/16
...
0.1110    with probability 1/16
0.1111    with probability 1/16

그러나 이것이 대부분의 프로그래머가 기대하는 분포인지 확실하지 않으므로 아마도 가치가 없을 것입니다. 또한 임의의 부동 소수점 값이 자주있는 것처럼 값을 사용하여 정수를 생성 할 때 많이 얻지 못합니다.)


답변