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


139

편집 : 기본적으로 내가 작성하려고하는 것은 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

43
나는 실제로 그 해답이 "LCG는 낮은 비트에서 낮은 엔트로피를 갖지"기보다는 임의의 부동 소수점 변이 생성에 대해 매혹적인 것이기를 바라고있다.
Sneftel

4
"1 비트 해시를 두 배로"의 목적은 무엇입니까? 나는 그러한 요구 사항을 합법적으로 적용 할 수 있다고 진지하게 생각하지 않습니다.
corsiKa

3
@corsiKa 기하학 계산에는 종종 두 가지 가능한 답 중에서 선택하고자하는 두 가지 사례가 있습니다 (예 : 왼쪽 또는 오른쪽을 가리킴)? 때로는 세 번째로 퇴보 된 사례를 소개합니다 (점은 오른쪽 라인)), 그러나 당신은 두 가지 가능한 답변을 가지고 있기 때문에 그 경우에 가능한 답변 중 하나를 의사 무작위로 선택해야합니다. 내가 생각할 수있는 가장 좋은 방법은 주어진 double 값 중 하나의 1 비트 해시를 취하는 것입니다 (기하학적 계산이므로 모든 곳에 double이 있음을 기억하십시오).
gvlasov

2
@corsiKa (너무 길기 때문에 주석을 2로 나눈 값) 우리는 더 간단한 것으로 시작할 수는 doubleValue % 1 > 0.5있지만 어떤 경우에는 가시적 인 규칙 성을 도입 할 수 있기 때문에 너무 거칠다 (모든 값은 길이 1의 범위 내에 있음). 그것이 너무 거칠다면, 우리는 아마도 더 작은 범위를 시도해야 doubleValue % 1e-10 > 0.5e-10합니까? 그래 그리고 마지막 비트를 a의 해시로 취하는 double것은 모듈로를 최소화 하면서이 접근법을 끝까지 수행 할 때 발생 하는 것 입니다.
gvlasov

1
@kmote는 여전히 중요하게 가장 중요도가 낮은 비트를 유지하고 다른 비트는 그것을 보상하지 않습니다. 실제로 정확히 같은 이유로 0을 향하여 바이어스됩니다 (그러나 덜). 따라서 분포는 약 50, 12.5, 25, 12.5입니다. (lastbit & 3) == 0그래도 이상하게 작동합니다.
해롤드

답변:


165

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 참조) .


16
만세! 내가 바랐던 것.
Sneftel

3
@Matt 아마도 속도 최적화 일 것입니다. 대안은 기하 분포를 사용하여 지수를 생성 한 다음 가수를 별도로 생성하는 것입니다.
Sneftel

7
@ 매트 : "최고"를 정의하십시오. random.nextDouble()는 일반적으로 의도 된대로 "최상의"방법이지만 대부분의 사람들은 임의의 이중으로부터 1 비트 해시를 만들려고하지 않습니다. 균일 한 분포, 암호 분석에 대한 내성 또는 무엇을 찾고 있습니까?
StriplingWarrior

1
이 답변은 OP에 임의의 숫자에 2 ^ 53을 곱하고 결과 정수가 홀수인지 확인한 경우 50/50 분포가 있었음을 나타냅니다.
rici

4
The111 @는 말한다 여기에next을 반환해야 int는 32 비트까지 할 수 있도록 어쨌든,
해롤드

48

로부터 문서 :

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
이상한. 방금 Java 8에서 이것을 재현했습니다.
aioobe

1
이제는 편견이 여전히 새로운 방법에 적용된다고 주장했기 때문에 흥미 롭습니다. 내가 잘못?
해롤드

3
@ harold : 아니요, 당신이 옳고이 편견을 고치려고 한 사람은 실수를 한 것 같습니다.
Thomas

6
@harold 시간을 Java 사람에게 이메일을 보내십시오.
Daniel

8
"아마도 고정 버전은 테스트되지 않았습니까?" 실제로, 이것을 다시 읽으면서, 그 문서는 다른 문제에 관한 것이라고 생각합니다. 그것을 언급합니다 반올림 그들이 문제, 비 균일 한 분포이 리드 값이 때 직접, 오히려 것을 할 "가능성으로 세 번"는 고려하지 않았 음을 시사하는 둥근 . 내 대답에서, 내가 나열한 값은 균일하게 분포되어 있지만 IEEE 형식으로 표현 된 하위 비트는 균일하지 않습니다. 그들이 고친 문제는 낮은 비트의 균일 성이 아니라 전반적인 균일 성과 관련이 있다고 생각합니다.
ajb

33

부동 소수점 숫자가 표현되는 방식을 고려할 때이 결과는 놀랍지 않습니다. 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

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


5
부동 소수점을 사용하여 임의의 비트 / 바이트 / 아무것도 얻으면 어쨌든 혼란 스럽습니다. 0과 n 사이의 임의 분포에
대해서도
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.