Почему rand() + rand() создает отрицательные числа?

Я заметил, что функция библиотеки 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()));
}

Может кто-нибудь объяснить, почему во втором случае я вижу отрицательные числа?

PS: Я инициализирую семя перед циклом как srand(time(NULL)).

Ответ 1

rand() определено для возврата целого числа между 0 и RAND_MAX.

rand() + rand()

может переполняться. То, что вы наблюдаете, вероятно, является результатом undefined поведения, вызванного переполнением целых чисел.

Ответ 2

Проблема заключается в добавлении. rand() возвращает значение int 0...RAND_MAX. Итак, если вы добавите два из них, вы получите до RAND_MAX * 2. Если это превышает INT_MAX, результат добавления переполняет допустимый диапазон, который может содержать int. Переполнение значных значений undefined и может привести к тому, что ваша клавиатура будет разговаривать с вами на иностранных языках.

Поскольку здесь нет выигрыша при добавлении двух случайных результатов, простая идея состоит в том, чтобы просто не делать этого. В качестве альтернативы вы можете сделать каждый результат до unsigned int до добавления, если это может удерживать сумму. Или используйте больший тип. Обратите внимание, что long не обязательно шире, чем int, то же самое относится к long long, если int не менее 64 бит!

Заключение: просто избегайте добавления. Это не дает больше "случайности". Если вам нужно больше битов, вы можете объединить значения sum = a + b * (RAND_MAX + 1), но для этого, вероятно, также потребуется больший тип данных, чем int.

Как ваша заявленная причина состоит в том, чтобы избежать нулевого результата: этого нельзя избежать, добавив результаты двух вызовов rand(), так как оба могут быть равны нулю. Вместо этого вы можете просто увеличить. Если RAND_MAX == INT_MAX, это невозможно сделать в int. Однако (unsigned int)rand() + 1 сделает очень, очень вероятно. Вероятно (не окончательно), потому что он требует UINT_MAX > INT_MAX, который я не уверен, гарантирован, но действителен для всех реализаций, о которых я действительно знаю (и это не просто x86/64 или ARM).

Внимание:

Несмотря на то, что здесь уже посыпаны комментарии, обратите внимание, что добавление двух случайных значений не получает равномерного распределения, но треугольное распределение, например, перекатывание двух кубиков: чтобы получить 12 (две кости), обе кости должны показать 6, для 11 уже есть два возможных варианта: 6 + 5 или 5 + 6 и т.д.

Таким образом, добавление также плохо из этого аспекта.

Также обратите внимание, что генерируемые результаты rand() не являются независимыми друг от друга, поскольку они генерируются генератором псевдослучайных чисел . Отметим также, что стандарт не определяет качество или равномерное распределение вычисленных значений.

Ответ 3

Это ответ на разъяснение вопроса, сделанного в комментарии к этому ответу,

причина, по которой я добавлял, заключалась в том, чтобы избежать "0" в качестве случайного числа в моем коде. rand() + rand() - это быстрое грязное решение, которое легко пришло мне в голову.

Проблема заключалась в том, чтобы избежать 0. Существует (по крайней мере) две проблемы с предлагаемым решением. Один из них, как показывают другие ответы, указывает, что rand()+rand() может вызывать поведение undefined. Лучший совет - никогда не ссылаться на поведение undefined. Другая проблема заключается в том, что нет гарантии, что rand() не будет генерировать 0 дважды подряд.

Следующие отклонения ноль, избегают поведения undefined и в подавляющем большинстве случаев будут быстрее, чем два вызова rand():

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

Ответ 4

В основном 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;
}

Ответ 5

Возможно, вы могли бы попробовать довольно хитрый подход, убедившись, что значение, возвращаемое суммой 2 rand(), никогда не превышает значение RAND_MAX. Возможным подходом может быть сумма = rand()/2 + rand()/2; Это обеспечит, что для 16-битного компилятора с RAND_MAX значением 32767, даже если оба rand возвращаются 32767, даже тогда (32767/2 = 16383) 16383 + 16383 = 32766, таким образом, не приведет к отрицательной сумме.

Ответ 6

Чтобы избежать 0, попробуйте следующее:

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

Вам нужно включить limits.h.

Извините за мой плохой английский.

Ответ 7

причина, по которой я добавлял, заключалась в том, чтобы избежать "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

Это ограничит ваше максимальное значение, но если вас это не волнует, тогда это будет хорошо работать для вас.

Ответ 8

В то время как все остальные говорили о вероятном переполнении, вполне могут быть причиной отрицательного, даже если вы используете целые числа без знака. Реальная проблема заключается в том, что в качестве семени используется функция времени/даты. Если вы действительно познакомитесь с этой функциональностью, вы точно поймете, почему я это говорю. Как то, что он на самом деле делает, это дать дистанцию ​​(прошедшее время) с определенной даты/времени. Хотя использование функции даты/времени в качестве семени для rand(), является очень распространенной практикой, это действительно не самый лучший вариант. Вы должны искать лучшие альтернативы, так как существует множество теорий по этой теме, и я не мог уйти во все их. Вы добавляете в это уравнение возможность переполнения, и этот подход был обречен с самого начала.

Те, кто разместил rand() + 1, используют решение, которое больше всего используется, чтобы гарантировать, что они не получат отрицательное число. Но этот подход действительно не самый лучший способ.

Лучшее, что вы можете сделать, - это дополнительное время для написания и использования правильной обработки исключений и только добавление к номеру rand(), если и/или когда вы закончите с нулевым результатом. И, чтобы справиться с отрицательными цифрами должным образом. Функциональность rand() не идеальна и поэтому должна использоваться в сочетании с обработкой исключений, чтобы гарантировать, что вы получите желаемый результат.

Привлечение дополнительного времени и усилий для исследования, изучения и правильного внедрения функций rand() - это хорошо стоит времени и усилий. Только мои два цента. Удачи в ваших начинаниях...