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

309

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

  • 11
    rand() не может быть отрицательным ...
  • 285
    rand () + rand () может owerflow
Показать ещё 20 комментариев
Теги:
random

8 ответов

501
Лучший ответ

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

rand() + rand()

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

  • 0
    Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
  • 0
    На самом деле это будет зависеть, если rand вернет целое число со знаком или без знака, так как переполнение для целых чисел без знака хорошо определено.
Показать ещё 11 комментариев
76

Проблема заключается в добавлении. 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() не являются независимыми друг от друга, поскольку они генерируются генератором псевдослучайных чисел . Отметим также, что стандарт не определяет качество или равномерное распределение вычисленных значений.

  • 0
    Спасибо. причина, которую я добавил, состояла в том, чтобы избежать '0' как случайного числа в моем коде. rand () + rand () было быстрым грязным решением, которое мне пришло в голову.
  • 14
    @badmad: Так что, если оба вызова вернут 0?
Показать ещё 14 комментариев
34

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

причина, по которой я добавлял, заключалась в том, чтобы избежать "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);
  • 8
    Что насчет rand() + 1 ?
  • 3
    @askvictor Это может переполниться (хотя это маловероятно).
Показать ещё 10 комментариев
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. Возможным подходом может быть сумма = rand()/2 + rand()/2; Это обеспечит, что для 16-битного компилятора с RAND_MAX значением 32767, даже если оба rand возвращаются 32767, даже тогда (32767/2 = 16383) 16383 + 16383 = 32766, таким образом, не приведет к отрицательной сумме.

  • 1
    ОП хотел исключить 0 из результатов. Сложение также не обеспечивает равномерного распределения случайных значений.
  • 0
    @Olaf: нет никакой гарантии, что два последовательных вызова rand() не дадут оба ноль, поэтому желание избежать нуля не является хорошей причиной для добавления двух значений. С другой стороны, желание иметь неравномерное распределение было бы хорошей причиной для добавления двух случайных значений, если одно гарантирует, что переполнение не произойдет.
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
    Sidenote: Осторожно с правыми сдвигами знаковых переменных. Это только хорошо определено для неотрицательных значений, для отрицательных, это определено реализацией. (К счастью, rand() всегда возвращает неотрицательное значение). Однако я бы оставил здесь оптимизацию для компилятора.
  • 0
    @ Олаф: В общем, подписанное деление на два будет менее эффективным, чем смена. Если автор компилятора не приложил усилий к тому, чтобы сообщить компилятору, что rand будет неотрицательным, сдвиг будет более эффективным, чем деление на целое число со 2u 2. Деление на 2u может сработать, но если x - это int может привести к предупреждению о неявное преобразование из неподписанного в подписанное.
Показать ещё 5 комментариев
1

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

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

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

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

  • 4
    Это удвоит вероятность получить 1. Это в основном то же самое (но, возможно, медленнее), чем условное добавление 1, если rand() возвращает 0.
  • 0
    Да, ты прав, Олаф. Если rand () = 0 или INT_MAX -1, значение будет равно 1.
Показать ещё 2 комментария
-1

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

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

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

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

  • 2
    rand() не указывает, какое семя использовать. Стандарт делает это указание использовать псевдослучайный генератор, а не отношение к любому времени. В нем также не говорится о качестве генератора. На самом деле проблема явно в переполнении. Обратите внимание, что rand()+1 используется, чтобы избежать 0 ; rand() не возвращает отрицательное значение. Извините, но вы упустили момент здесь. Речь идет не о качестве PRNG. ...
  • 0
    ... Хорошая практика в GNU / Linux - это начинать с /dev/random и впоследствии использовать хороший PRNG (не уверенный в качестве rand() из glibc) или продолжать использовать устройство - рискуя приложением блокировать его, если есть недостаточно энтропии доступно. Попытка внести энтропию в приложение вполне может быть уязвимостью, так как ее легче атаковать. И теперь речь идет об упрочнении - не здесь

Ещё вопросы

Сообщество Overcoder
Наверх
Меню