Я заметил, что 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))
.
rand()
не может быть отрицательным ...RAND_MAX
для вашего компилятора? Обычно вы можете найти его вstdlib.h
. (Забавно: проверяюman 3 rand
, оно содержит однострочное описание «плохой генератор случайных чисел».)abs(rand()+rand())
. Я предпочел бы иметь положительный UB, чем отрицательный! ;)Ответы:
rand()
определен для возврата целого числа между0
иRAND_MAX
.может переполниться. То, что вы наблюдаете, вероятно, является результатом неопределенного поведения, вызванного целочисленным переполнением.
источник
a + b > a
знаком они могут предположить, что если они это знаютb > 0
. Они также могут предположить, что если позднее будет выполнен оператор,a + 5
то текущее значение будет нижеINT_MAX - 5
. Таким образом, даже на процессоре / интерпретаторе дополнения 2 без ловушек программа может вести себя не так, как если быint
s была дополнением 2 без ловушек.Проблема в дополнении.
rand()
возвращаетint
значение0...RAND_MAX
. Итак, если вы добавите два из них, вы получите доRAND_MAX * 2
. Если это превышаетINT_MAX
, результат сложения переполняет допустимый диапазон, которыйint
может содержать. Переполнение значений со знаком является неопределенным поведением и может привести к тому, что ваша клавиатура разговаривает с вами на иностранных языках.Поскольку здесь нет никакого смысла в добавлении двух случайных результатов, простая идея - просто не делать этого. В качестве альтернативы вы можете разыграть каждый результат
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
, что верно для всех реализаций, о которых я знаю (которые охватывают довольно много встроенных архитектур, DSP и всех настольных, мобильных и серверных платформ последних 30 лет).Предупреждение:
Хотя здесь уже добавлены комментарии, обратите внимание, что при добавлении двух случайных значений не получается равномерное распределение, а треугольное распределение, например, бросание двух кубиков: чтобы получить
12
(два кубика), нужно показать оба кубика6
. поскольку11
уже есть два возможных варианта:6 + 5
или5 + 6
и т. д.Таким образом, дополнение также плохо с этой стороны.
Также обратите внимание, что
rand()
генерируемые результаты не являются независимыми друг от друга, так как они генерируются генератором псевдослучайных чисел . Отметим также, что в стандарте не указывается качество или равномерное распределение рассчитанных значений.источник
UINT_MAX > INT_MAX != false
гарантируется ли стандарт. (Звучит вероятно, но не уверен, если требуется). Если это так, вы можете просто привести один результат и приращение (в таком порядке!).Это ответ на уточнение вопроса, сделанный в комментарии к этому ответу ,
Проблема состояла в том, чтобы избежать 0. Есть (по крайней мере) две проблемы с предлагаемым решением. Один из них, как указывают другие ответы,
rand()+rand()
может вызывать неопределенное поведение. Лучший совет - никогда не вызывать неопределенное поведение. Другая проблема заключается в том, что нет никакой гарантии, чтоrand()
0 не получится дважды подряд.Следующее отклоняет ноль, избегает неопределенного поведения и в подавляющем большинстве случаев будет быстрее, чем два вызова
rand()
:источник
rand() + 1
?В основном
rand()
производите числа между0
иRAND_MAX
, и2 RAND_MAX > INT_MAX
в вашем случае.Вы можете модулировать с максимальным значением вашего типа данных, чтобы предотвратить переполнение. Это конечно нарушит распределение случайных чисел, но
rand
это просто способ получить быстрые случайные числа.источник
Возможно, вы могли бы попробовать довольно хитрый подход, убедившись, что значение, возвращаемое суммой 2 rand (), никогда не превышает значение RAND_MAX. Возможным подходом может быть sum = rand () / 2 + rand () / 2; Это гарантирует, что для 16-битного компилятора со значением RAND_MAX 32767, даже если оба rand возвращают 32767, даже тогда (32767/2 = 16383) 16383 + 16383 = 32766, таким образом, не будет получена отрицательная сумма.
источник
rand()
оба не приведут к нулю, поэтому желание избежать нуля не является хорошей причиной для добавления двух значений. С другой стороны, желание иметь неравномерное распределение было бы хорошей причиной для добавления двух случайных значений, если одно гарантирует, что переполнение не произойдет.Простое решение (хорошо, назовите это «Hack»), которое никогда не приводит к нулевому результату и никогда не будет переполнено:
Это ограничит вашу максимальную ценность, но если вы не заботитесь об этом, то это должно работать для вас.
источник
rand()
всегда возвращает неотрицательное значение). Однако я бы оставил здесь оптимизацию для компилятора.rand
он будет неотрицательным, сдвиг будет более эффективным, чем деление на целое число со знаком 2. Деление на2u
может работать, но еслиx
этоint
может привести к предупреждению о неявном преобразовании из неподписанного подписать/ 2
любом случае (я видел это даже для чего-то вроде-O0
, то есть без явно заданных оптимизаций). Это, пожалуй, самая тривиальная и наиболее устоявшаяся оптимизация кода на C. Точка деления хорошо определяется стандартом для всего целочисленного диапазона, а не только для неотрицательных значений. Опять же: оставьте жалобы компилятору, прежде всего напишите правильный и понятный код. Это даже более важно для начинающих.rand()
вправо на единицу или делении на,2u
чем при делении на 2, даже при использовании-O3
. Можно было бы разумно сказать, что такая оптимизация вряд ли имеет значение, но выражение «оставить такие оптимизации для компилятора» будет означать, что компиляторы, скорее всего, их выполнят. Вы знаете какие-нибудь компиляторы, которые на самом деле будут?Чтобы избежать 0, попробуйте это:
Вы должны включить
limits.h
.источник
rand()
приводит к 0.1
и2
(все предполагаетсяRAND_MAX == INT_MAX
). Я забыл о- 1
.-1
Здесь не служит никакой ценности.rand()%INT_MAX+1;
будет по-прежнему генерировать только значения в диапазоне [1 ... INT_MAX].Хотя то, что все остальные говорили о вероятном переполнении, вполне может быть причиной негатива, даже если вы используете целые числа без знака. Реальная проблема на самом деле заключается в использовании функции времени / даты в качестве начального числа. Если вы действительно познакомились с этой функциональностью, вы точно поймете, почему я так говорю. То, что он на самом деле делает, это дает расстояние (прошедшее время) с данной даты / времени. Хотя использование функции даты / времени в качестве семени для rand () является очень распространенной практикой, на самом деле это не лучший вариант. Вы должны искать лучшие альтернативы, так как по этой теме существует множество теорий, и я не смог бы рассмотреть все из них. Вы добавляете в это уравнение возможность переполнения, и этот подход был обречен с самого начала.
Те, кто разместил rand () + 1, используют решение, которое используется чаще всего, чтобы гарантировать, что они не получат отрицательное число. Но такой подход тоже не самый лучший.
Лучшее, что вы можете сделать, - это потратить дополнительное время на написание и использование правильной обработки исключений, и только добавить к числу rand (), если и / или когда вы получите нулевой результат. И правильно обращаться с отрицательными числами. Функциональность rand () не идеальна и поэтому должна использоваться в сочетании с обработкой исключений, чтобы обеспечить желаемый результат.
Потратив дополнительное время и усилия на изучение, изучение и правильную реализацию функциональности rand (), стоит своих усилий и времени. Просто мои два цента. Удачи в ваших начинаниях ...
источник
rand()
не указывает, какое семя использовать. Стандарт делает указание его использовать псевдослучайный генератор, а не отношение к любому времени. В нем также не говорится о качестве генератора. На самом деле проблема явно в переполнении. Обратите внимание, чтоrand()+1
используется, чтобы избежать0
;rand()
не возвращает отрицательное значение. Извините, но вы упустили момент здесь. Речь идет не о качестве PRNG. .../dev/random
и последующее использование хорошего PRNG (не уверенного в качествеrand()
от glibc) или продолжение использования устройства - рискуя тем, что ваше приложение блокируется, если энтропии недостаточно. Попытка внести энтропию в приложение вполне может быть уязвимостью, так как ее легче атаковать. И теперь дело доходит до ужесточения - не здесь