rand () + rand ()가 음수를 생성하는 이유는 무엇입니까?


304

rand()루프 내에서 한 번만 호출되면 라이브러리 함수는 거의 항상 양수를 생성 한다는 것을 관찰했습니다 .

for (i = 0; i < 100; i++) {
    printf("%d\n", rand());
}

그러나 두 개의 rand()통화를 추가 하면 생성 된 숫자에 더 많은 음수가 있습니다.

for (i = 0; i < 100; i++) {
    printf("%d = %d\n", rand(), (rand() + rand()));
}

누군가 두 번째 경우에 왜 음수가 표시되는지 설명 할 수 있습니까?

추신 : 루프 전에 시드를로 초기화합니다 srand(time(NULL)).


11
rand()부정적인 수 없습니다 ...
twentylemon

293
rand () + rand () can owerflow
maskacovnik

13
RAND_MAX컴파일러 는 무엇입니까 ? 일반적으로에서 찾을 수 있습니다 stdlib.h. (재미 있은 : 검사는 man 3 rand, 그것은 한 줄 설명 "나쁜 난수 발생기를"곰.)
usr2564301

6
모든 제정신 프로그래머가하는 일을하십시오 abs(rand()+rand()). 오히려 부정적인 것보다 긍정적 인 UB를 갖고 싶습니다! ;)
Vinicius Kamakura

11
@hexa : 이미 추가에 대해 발생하는 것처럼 UB에 대한 부담은 없습니다. UB를 정의 된 동작으로 만들 수 없습니다 . 제정신의 progrtammer은 지옥 같은 UB을 피할 것.
이 사이트에 대해 너무 정직합니다

답변:


542

rand()0와 사이의 정수를 반환하도록 정의되었습니다 RAND_MAX.

rand() + rand()

넘칠 수 있습니다. 관찰 한 것은 정수 오버플로로 인한 정의되지 않은 동작 의 결과 일 수 있습니다 .


4
@JakubArnold : 오버플로 동작이 언어마다 다르게 지정되는 방법은 무엇입니까? 예를 들어 파이썬은 int가 커짐에 따라 (사용 가능한 메모리까지) 아무것도 없습니다.
이 사이트에 대해 너무 정직합니다

2
@Olaf 언어가 부호있는 정수를 나타내는 방법에 따라 다릅니다. Java 는 정수 오버플로를 감지하는 메커니즘이 없었으며 (Java 8까지) 랩핑하도록 정의했으며 Go 는 2의 보수 표현 만 사용하고 부호있는 정수 오버플로에 대해 합법적으로 정의합니다. C는 분명히 2 이상의 보수를 지원합니다.
PP

2
@EvanCarslake 아니오, 그것은 보편적 인 행동이 아닙니다. 당신이 말하는 것은 2의 보수 표현에 관한 것입니다. 그러나 C 언어는 다른 표현도 허용합니다. C 언어 사양에 따르면 부호있는 정수 오버플로가 정의되지 않았습니다 . 따라서 일반적으로 어떤 프로그램도 그러한 동작에 의존해서는 안되며 부호있는 정수 오버플로가 발생하지 않도록 신중하게 코딩해야합니다. 그러나 서명되지 않은 정수는 잘 정의 된 (환원 모듈로 2) 방식으로 "랩 어라운드"되므로 적용 할 수 없습니다. [계속] ...
PP

12
부호있는 정수 오버플로와 관련된 C 표준의 인용문 입니다. 표현식을 평가하는 동안 예외 조건이 발생하는 경우 (즉, 결과가 수학적으로 정의되지 않았거나 해당 유형의 표현 가능한 값 범위에없는 경우) 동작 정의되지 않았습니다.
PP

3
@EvanCarslake는 C 컴파일러가 표준을 사용하고 부호있는 정수에 대해 질문을 조금 멀리 이동하면 a + b > a알 수 있다고 가정 할 수 있습니다 b > 0. 또한 나중에 실행 된 명령문이 있으면 a + 5현재 값이 그보다 낮다고 가정 할 수 있습니다 INT_MAX - 5. 따라서 트랩이없는 2의 보수 프로세서 / 통역 프로그램에서도 트랩이없는 int2의 보수 처럼 작동하지 않을 수 있습니다 .
Maciej Piechotka

90

문제는 추가입니다. 값을 rand()반환합니다 . 따라서 두 개를 추가하면에 도달 할 수 있습니다. 이 초과 하면 추가 결과가 보유 할 수 있는 유효 범위를 초과합니다 . 부호있는 값의 오버플로는 정의되지 않은 동작이며 키보드가 외국 언어로 대화 할 수 있습니다.int0...RAND_MAXRAND_MAX * 2INT_MAXint

여기에 두 개의 임의 결과를 추가 할 때 얻는 이점이 없으므로 간단한 아이디어는 그렇게하지 않는 것입니다. 또는 unsigned int합계를 보유 할 수있는 경우 각 결과를 더하기 전에 캐스트 할 수 있습니다 . 또는 더 큰 유형을 사용하십시오. 참고 long보다 반드시 더 넓은 int동일한가 적용되는 long long경우에 int적어도 64 비트이다!

결론 : 추가를 피하십시오. 더 많은 "무작위"를 제공하지 않습니다. 더 많은 비트가 필요하면 값을 연결할 수도 sum = a + b * (RAND_MAX + 1)있지만보다 큰 데이터 유형이 필요할 수도 있습니다 int.

명시된 이유는 제로 결과를 피하는 것입니다. 두 rand()통화가 모두 0 일 수 있으므로 두 호출 의 결과를 추가하여 피할 수는 없습니다 . 대신 증분 할 수 있습니다. 경우 RAND_MAX == INT_MAX,이은으로 수행 할 수 없습니다 int. 그러나 (unsigned int)rand() + 1매우 가능성이 높습니다. UINT_MAX > INT_MAX필자가 알고있는 모든 구현에서 사실 이기 때문에 (최종적으로 아님) 가능성이 높습니다 ( 지난 30 년 동안 임베디드 아키텍처, DSP 및 모든 데스크탑, 모바일 및 서버 플랫폼을 포함합니다).

경고:

이미 여기 의견에 뿌려 있지만,이 개 임의의 값을 추가하면 않는하시기 바랍니다 참고 하지 얻을 : 균일 한 분포를 얻을 수 있지만, 두 개의 주사위를 압연 같은 삼각 분포는 12두 주사위 보여주고있다 (두 개의 주사위) 6. 를 위해 11: 거기에 이미 두 가지 변종이다 6 + 5또는 5 + 6등,

따라서이 측면에서도 추가가 나쁩니다.

또한 의사 난수 생성기에rand() 의해 생성되므로 결과 생성은 서로 독립적이지 않습니다 . 또한 표준은 계산 된 값의 품질 또는 균일 분포를 지정하지 않습니다.


14
@ badmad : 두 호출이 모두 0을 반환하면 어떻게됩니까?
이 사이트에 대해 너무 정직합니다

3
@ badmad : 나는 UINT_MAX > INT_MAX != false표준에 의해 보증 되는지 궁금합니다 . (소리가 들리지만 필요한 경우 확실하지 않습니다). 그렇다면 단일 결과를 캐스트하고 증가시킬 수 있습니다 (순서대로!).
이 사이트에 대해 너무 정직합니다.

3
균일하지 않은 분포를 원할 때 여러 개의 난수를 추가하면 이득이 있습니다 : stackoverflow.com/questions/30492259/…
Cœur

6
0을 피하기 위해, 간단한 "결과는 0이고, 다시 롤한다"?
Olivier Dulac

2
0을 피하는 나쁜 방법을 추가 할뿐만 아니라 균일하지 않은 분포를 초래합니다. 롤링 주사위의 결과와 같은 분포를 얻습니다. 7은 2 또는 12보다 6 배 높습니다.
Barmar

36

이것은이 답변에 대한 의견으로 제시된 질문을 명확히하는 답변입니다 .

내가 추가 한 이유는 내 코드에서 임의의 숫자로 '0'을 피하는 것이 었습니다. rand () + rand ()는 내 마음에 들었던 빠른 더러운 솔루션이었습니다.

문제는 0을 피하는 것이 었습니다. 제안 된 솔루션에는 (적어도) 두 가지 문제가 있습니다. 하나는 다른 답변에서 알 rand()+rand()수 있듯이 정의되지 않은 동작을 호출 할 수 있습니다. 최선의 조언은 정의되지 않은 동작을 절대 호출하지 않는 것입니다. 또 다른 문제는 rand()연속으로 0을 두 번 생성 하지 않는다는 보장이 없다는 것입니다.

다음은 0을 거부하고 정의되지 않은 동작을 피하며 대부분의 경우 두 번의 호출보다 빠릅니다 rand().

int rnum;
for (rnum = rand(); rnum == 0; rnum = rand()) {}
// or do rnum = rand(); while (rnum == 0);

9
무엇에 대해 rand() + 1?
askvictor

3
@askvictor 오버플로가 발생할 수 있습니다 (아마도).
gerrit

3
@gerrit-MAX_INT와 RAND_MAX에 의존
askvictor

3
@gerrit, 나는 그들이 동일 하지 않으면 놀랄 것입니다 , 그러나 이것은 이것이 아이들을위한 장소라고 생각합니다 :)
askvictor

10
RAND_MAX == MAX_INT 인 경우 rand () + 1은 rand ()의 값이 0 일 때와 정확히 동일한 확률로 오버플로되므로이 솔루션은 완전히 무의미합니다. 위험을 감수하고 오버플로 가능성을 무시하려는 경우 rand ()를 그대로 사용하고 0을 반환 할 가능성을 무시할 수 있습니다.
Emil Jeřábek 2016 년

3

기본적으로 rand()사이의 숫자를 생산 0하고 RAND_MAX, 그리고 2 RAND_MAX > INT_MAX귀하의 경우.

오버플로를 방지하기 위해 데이터 유형의 최대 값으로 계수를 계산할 수 있습니다. 이 과정은 난수 분포를 방해하지만 rand빠른 난수를 얻는 방법 일뿐입니다.

#include <stdio.h>
#include <limits.h>

int main(void)
{
    int i=0;

    for (i=0; i<100; i++)
        printf(" %d : %d \n", rand(), ((rand() % (INT_MAX/2))+(rand() % (INT_MAX/2))));

    for (i=0; i<100; i++)
        printf(" %d : %ld \n", rand(), ((rand() % (LONG_MAX/2))+(rand() % (LONG_MAX/2))));

    return 0;
}

2

2 rand ()의 합으로 리턴 된 값이 RAND_MAX의 값을 초과하지 않도록하여 다소 까다로운 접근법을 시도 할 수 있습니다. 가능한 접근 방식은 sum = rand () / 2 + rand () / 2; 이렇게하면 두 rand가 모두 32767을 반환하더라도 RAND_MAX 값이 32767 인 16 비트 컴파일러의 경우에도 (32767/2 = 16383) 16383 + 16383 = 32766이므로 음의 합계가되지 않습니다.


1
OP는 결과에서 0을 제외하려고했습니다. 또한 무작위 값의 균일 한 분포를 제공하지 않습니다.
이 사이트에 대한 너무 정직

@Olaf : 두 번의 연속 호출 rand()이 모두 0을 생성하지 않는다고 보장 할 수 없으므로 0을 피하려는 욕구가 두 값을 추가하는 좋은 이유는 아닙니다. 반면에 불균일 분포를 원한다면 오버플로가 발생하지 않도록 임의의 값 두 개를 더하는 것이 좋습니다.
supercat

1

내가 추가 한 이유는 내 코드에서 임의의 숫자로 '0'을 피하는 것이 었습니다. rand () + rand ()는 내 마음에 들었던 빠른 더러운 솔루션이었습니다.

간단한 해결책 (좋아, "Hack"이라고 함)은 결코 제로 결과를 생성하지 않으며 오버플로하지 않습니다.

x=(rand()/2)+1    // using divide  -or-
x=(rand()>>1)+1   // using shift which may be faster
                  // compiler optimization may use shift in both cases

이렇게하면 최대 가치가 제한되지만 신경 쓰지 않으면 잘 작동합니다.


1
주석 : 부호있는 변수의 올바른 이동에주의하십시오. 음수가 아닌 값에 대해서만 잘 정의되어 있으며 구현에 정의되어 있습니다. 운 좋게도 rand()항상 음수가 아닌 값을 반환합니다. 그러나 여기서는 최적화를 컴파일러에 맡길 것입니다.
이 사이트에 대해 너무 정직합니다

@Olaf : 일반적으로, 2로 서명 된 나누기는 교대보다 덜 효율적입니다. 컴파일러 작가가 컴파일러 말에 노력을 투자하지 않는 한 rand음이 아닌 것을, 시프트에 의해 서명 된 정수 2 부문으로 분할하는 것보다도 효율적 2u일 수 있지만, 경우에 x인은 int부호에서 암시 적 변환에 대한 경고가 발생할 수 있습니다 서명했다.
supercat

@ supercat : 내 의견 car3efully를 다시 읽으십시오. 합리적인 컴파일러가 / 2어쨌든 시프트를 사용한다는 것을 잘 알고 있어야 합니다 (나는 이것을 -O0명시 적으로 요청하지 않은 것과 같은 경우에도 보았습니다 ). 아마도 C 코드의 가장 사소하고 가장 확립 된 최적화 일 것입니다. 점은 음수가 아닌 값뿐만 아니라 전체 정수 범위의 표준에 의해 잘 정의되어 있습니다. 다시 : 컴파일러에 최적화를두고 처음부터 정확 하고 명확한 코드를 작성 하십시오 . 초보자에게는 더욱 중요합니다.
이 사이트에 대해 너무 정직합니다

@Olaf : 테스트 한 모든 컴파일러는을 사용할 때도 rand()1로 오른쪽으로 이동 하거나 2u2로 나눌 때보 다 더 효율적인 코드를 생성 -O3합니다. 그러한 최적화는 중요하지 않다고 합리적으로 말할 수 있지만 "컴파일러에 이러한 최적화를 그대로 두십시오"는 것은 컴파일러가이를 수행 할 가능성이 있음을 의미합니다. 당신은 알고 계십니까 어떤 실제로 것이라고 컴파일러?
supercat

@supercat : 그러면 최신 컴파일러를 사용해야합니다. gcc는 마지막으로 생성 된 어셈블러를 확인할 때 훌륭한 코드를 생성했습니다. 그럼에도 불구하고, 그루피를 갖기를 간절히 바라고 있기 때문에, 나는 당신이 마지막으로 제시 한 확장에 대해 괴롭히지 않는 것을 선호합니다. 이 게시물은 오래되었으며 내 의견은 완벽하게 유효합니다. 감사합니다.
이 사이트에 대해 너무 정직합니다.

1

0을 피하려면 다음을 시도하십시오.

int rnumb = rand()%(INT_MAX-1)+1;

포함해야합니다 limits.h.


4
즉, 기본적으로 동일 (단, possiblly 느린) 조건 1을 추가하는 등의 1을 얻을 수있는 확률을 두 배로 경우 rand()수익률 0
이 사이트에 대한 너무 정직

네, 당신은 맞아요 올라프입니다. rand () = 0 또는 INT_MAX -1이면 rnumb는 1이됩니다.
Doni

내가 생각하기에 더 나빠. 실제로 대한 성질을 가지고을 두 배로 1하고 2(모든 가정 RAND_MAX == INT_MAX). 나는 잊어 버렸습니다 - 1.
이 사이트에 대해 너무 정직합니다.

1
-1여기에 값을 제공하지 않습니다. rand()%INT_MAX+1; 여전히 [1 ... INT_MAX] 범위의 값만 생성합니다.
chux-Reinstate Monica

-2

부호없는 정수를 사용하더라도 오버플로 가능성에 대해 다른 사람들이 말한 것은 음의 원인이 될 수 있습니다. 실제 문제는 실제로 시간 / 날짜 기능을 시드로 사용하는 것입니다. 이 기능에 정통 해 졌다면 내가 왜 이런 말을하는지 정확히 알게 될 것입니다. 그것이 실제로하는 것은 주어진 날짜 / 시간 이후의 거리 (경과 시간)를 제공하는 것입니다. 날짜 / 시간 기능을 rand ()의 시드로 사용하는 것이 매우 일반적인 관행이지만 실제로는 최상의 옵션이 아닙니다. 주제에 대한 많은 이론이 있으며 모든 것을 다룰 수는 없으므로 더 나은 대안을 찾아야합니다. 이 방정식에 오버플로 가능성을 추가하면이 접근 방식은 처음부터 끝났습니다.

rand () + 1을 게시 한 사람들은 음수가되지 않도록하기 위해 가장 많이 사용하는 솔루션을 사용하고 있습니다. 그러나 그 접근 방식은 실제로 가장 좋은 방법은 아닙니다.

최선의 방법은 적절한 예외 처리를 작성하고 사용하는 데 추가 시간이 걸리고 결과가 없거나 결과가 없을 때만 rand () 숫자에 추가하는 것입니다. 그리고 음수를 올바르게 처리합니다. rand () 기능은 완벽하지 않으므로 원하는 결과를 얻을 수 있도록 예외 처리와 함께 사용해야합니다.

rand () 기능을 조사, 연구 및 올바르게 구현하기 위해 추가 시간과 노력을 투자하는 것은 시간과 노력의 가치가 있습니다. 내 두 센트. 당신의 노력에 행운을 빕니다 ...


2
rand()사용할 시드를 지정하지 않습니다. 표준 의사 랜덤 생성기를 사용하도록 지정하고 있으며, 시간과 관련이 없습니다. 또한 발전기의 품질에 대해서는 언급하지 않습니다. 실제 문제는 분명히 오버플로입니다. 참고 rand()+1방지하기 위해 사용된다 0; rand()음수 값을 반환하지 않습니다. 죄송하지만 여기서 요점을 놓쳤습니다. PRNG의 품질에 관한 것이 아닙니다. ...
이 사이트에 대해 너무 정직합니다

... GNU / Linux /dev/random에서는 좋은 PRNG 를 시드 하고 그 이후에 좋은 PRNG를 rand()사용하거나 (glibc 의 품질에 대해 확실하지 않음 ) 장치를 계속 사용하는 것이 좋습니다. 엔트로피가 충분하지 않으면 응용 프로그램이 차단 될 위험이 있습니다. 응용 프로그램에서 엔트로피를 얻는 것은 공격하기 쉬운 취약점 일 수 있습니다. 그리고 지금 그것은 강화되지 않습니다-여기가 아닙니다
이 사이트에 대해 너무 솔직합니다
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.