Я реализовывал хэш-карту в C как часть проекта, над которым я работаю, и использовал случайные вставки для его проверки, когда заметил, что rand()
в Linux кажется, что цифры повторяются гораздо чаще, чем в Mac. RAND_MAX
равно 2147483647 / 0x7FFFFFFF на обеих платформах. Я сократил его до этой тестовой программы, которая создает массив байтов RAND_MAX+1
длиной, генерирует RAND_MAX
случайные числа, отмечает, если каждое из них является дубликатом, и проверяет его из списка, как видно.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main() {
size_t size = ((size_t)RAND_MAX) + 1;
char *randoms = calloc(size, sizeof(char));
int dups = 0;
srand(time(0));
for (int i = 0; i < RAND_MAX; i++) {
int r = rand();
if (randoms[r]) {
// printf("duplicate at %d\n", r);
dups++;
}
randoms[r] = 1;
}
printf("duplicates: %d\n", dups);
}
Linux последовательно генерирует около 790 миллионов дубликатов. Mac последовательно генерирует только одно, поэтому он просматривает каждое случайное число, которое может генерировать почти без повторения. Может кто-нибудь объяснить мне, как это работает? Я не могу сказать ничего отличного от man-страниц, не могу сказать, какой RNG использует каждый, и не могу найти что-либо в Интернете. Спасибо!
Ответы:
Хотя на первый взгляд может показаться, что macOS
rand()
лучше не повторять никаких чисел, следует отметить, что при таком количестве генерируемых чисел ожидается множество дубликатов (на самом деле около 790 миллионов или (2 31 -1). ) / е ). Аналогичным образом, повторение чисел в последовательности также не приведет к дублированию, но не будет считаться очень случайным. Таким образом,rand()
реализация Linux в этом тесте неотличима от истинного случайного источника, тогда как macOSrand()
- нет.Еще одна вещь, которая на первый взгляд кажется удивительной, - это то, как macOS
rand()
может так хорошо избегать дублирования. Глядя на его исходный код , мы обнаруживаем, что реализация выглядит следующим образом:Это действительно приводит ко всем числам от 1 до
RAND_MAX
, включительно, ровно один раз, прежде чем последовательность повторяется снова. Поскольку следующее состояние основано на умножении, состояние никогда не может быть нулевым (или все будущие состояния также будут равны нулю). Таким образом, повторное число, которое вы видите, является первым, а ноль - тем, которое никогда не возвращается.Apple продвигает использование лучших генераторов случайных чисел в своей документации и примерах, по крайней мере, до тех пор, пока существует macOS (или OS X), поэтому качество,
rand()
вероятно, не считается важным, и они просто придерживались одного из самые простые из доступных псевдослучайных генераторов. (Как вы заметили, ихrand()
даже комментируют с рекомендацией использоватьarc4random()
вместо этого.)На заметку о том, что самый простой генератор псевдослучайных чисел, который я нашел, который дает достойные результаты в этом (и многих других) тестах на случайность, это xorshift * :
Результатом этой реализации будет почти ровно 790 миллионов дубликатов в вашем тесте.
источник
arc4random()
подобный кодrand()
и получить хорошийrand()
результат. Вместо того чтобы пытаться заставить программистов по-другому писать код, просто создайте лучшие библиотечные функции. «Они только что застряли» - это их выбор.rand()
делает его настолько плохим, что это бесполезно для практического использования: почему rand ()% 7 всегда возвращает 0? , Rand ()% 14 генерирует только значения 6 или 13rand
, чтобы при повторном запуске с тем же начальным числом создавалась та же последовательность. OpenBSD неrand
работает и не подчиняется этому контракту.rand()
с одним и тем же начальным числом создавалась одинаковая последовательность между разными версиями библиотеки? Такая гарантия может быть полезна для регрессионного тестирования между версиями библиотеки, но я не нахожу требований к Си для этого.MacOS предоставляет недокументированную функцию rand () в stdlib. Если оставить его незаполненным, то первые значения, которые он выводит, это 16807, 282475249, 1622650073, 984943658 и 1144108930. Быстрый поиск покажет, что эта последовательность соответствует основному генератору случайных чисел LCG, который выполняет следующую формулу:
Поскольку состояние этого RNG полностью описывается значением одного 32-разрядного целого числа, его период не очень велик. Чтобы быть точным, он повторяется каждые 2 31 - 2 итерации, выводя каждое значение от 1 до 2 31 - 2.
Я не думаю, что есть стандартная реализация rand () для всех версий Linux, но есть функция glibc rand (), которая часто используется. Вместо одной 32-битной переменной состояния, в ней используется пул из более чем 1000 битов, который, по сути, никогда не будет создавать полностью повторяющуюся последовательность. Опять же, вы, вероятно, можете узнать, какая у вас версия, распечатав первые несколько выводов с этого ГСЧ, не заполнив его сначала. (Функция glibc rand () создает числа 1804289383, 846930886, 1681692777, 1714636915 и 1957747793.)
Поэтому причина того, что вы получаете больше коллизий в Linux (и вряд ли в MacOS), заключается в том, что версия rand () для Linux в основном более случайная.
источник
rand()
должен вести себя как один сsrand(1);
rand()
Доступен исходный код для MacOS: opensource.apple.com/source/Libc/Libc-1353.11.2/stdlib/FreeBSD/… FWIW, я выполнил тот же тест для этого, скомпилированного из исходного кода, и он действительно приводит к только один дубликат. Apple продвигает использование других генераторов случайных чисел (например,arc4random()
до того, как Swift вступил во владение) в их примерах и документации, поэтому,rand()
вероятно, использование не очень распространено в нативных приложениях на их платформах, что может объяснить, почему это не лучше.rand()
, что у вас нет документов, но @Arkku предоставила ссылку на очевидный источник. Кто-нибудь из вас знает, почему я не могу найти этот файл в моей системе и почему я вижу толькоint rand(void) __swift_unavailable("Use arc4random instead.");
в Macstdlib.h
? Я предполагаю, что код @Arkku, на который ссылается, только что скомпилирован в ... какую библиотеку?/usr/lib/libc.dylib
. =)rand()
данной программы , использования C не определяется «компилятор» или «операционной системы», а скорее реализации стандартной библиотеки С (например,glibc
,libc.dylib
,msvcrt*.dll
).rand()
определяется стандартом C, а стандарт C не определяет, какой алгоритм использовать. Очевидно, что Apple использует подчиненный алгоритм для вашей реализации GNU / Linux: Linux не отличается от истинного случайного источника в вашем тесте, в то время как реализация Apple просто перемешивает числа вокруг.Если вам нужны случайные числа любого качества, либо используйте более качественный PRNG, который дает хотя бы некоторые гарантии качества возвращаемых чисел, либо просто считываете с него
/dev/urandom
или тому подобное. Последний дает вам криптографическое качество, но это медленно. Даже если он сам по себе слишком медленный, он/dev/urandom
может обеспечить отличные семена для других, более быстрых PRNG.источник
В общем, пара rand / srand долгое время считалась устаревшей из-за того, что биты младшего разряда отображают меньшую случайность, чем биты старшего разряда в результатах. Это может или не может иметь какое-либо отношение к вашим результатам, но я думаю, что это все еще хорошая возможность помнить, что, хотя некоторые реализации rand / srand теперь более современны, более старые реализации сохраняются, и лучше использовать случайные (3 ). На моем компьютере Arch Linux следующее примечание все еще находится на странице руководства для rand (3):
Чуть ниже справочная страница на самом деле дает очень короткие, очень простые примеры реализации rand и srand, которые относятся к самым простым LC RNG, которые вы когда-либо видели, и имеют небольшой RAND_MAX. Я не думаю, что они соответствуют тому, что находится в стандартной библиотеке C, если они когда-либо делали. Или, по крайней мере, я надеюсь, что нет.
В общем, если вы собираетесь использовать что-то из стандартной библиотеки, используйте случайное, если можете (на странице руководства он перечисляется как стандарт POSIX до POSIX.1-2001, но rand является стандартным способом еще до того, как C был даже стандартизирован) , Или, что еще лучше, взломайте Numeric Recipes (или поищите его в Интернете) или Knuth и реализуйте один. Они действительно просты, и вам действительно нужно сделать это один раз, чтобы получить универсальный ГСЧ с атрибутами, которые вам чаще всего нужны и которые имеют известное качество.
источник
rand()
«лучше» означало бы сделать его медленнее (что, вероятно, и так - случайные числа с криптографической защитой требуют больших усилий), то, вероятно, лучше сохранить его быстрым, даже если он будет несколько более предсказуемым. Пример: у нас было производственное приложение, для запуска которого потребовались целые годы, и мы проследили его до ГСЧ, инициализация которого требовала ожидания генерирования достаточной энтропии ... Оказалось, что оно не должно быть настолько безопасным, поэтому заменив его на «худший» ГСЧ был большим улучшением.