Я хочу получить равномерное распределение в диапазоне [0.0, 1.0)
Если возможно, позвольте реализации использовать случайные байты из / dev / urandom.
Также было бы неплохо, если бы ваше решение было потокобезопасный. Если вы не уверены, укажите это.
См. какое-то решение, о котором я подумал после прочтения других ответов.





#include <stdlib.h>
printf("%f\n", drand48());
double c;
fd = open("/dev/random", O_RDONLY);
unsigned int a, b;
read(fd, &a, sizeof(a));
read(fd, &b, sizeof(b));
if (a > b)
c = fabs((double)b / (double)a);
else
c = fabs((double)a / (double)b);
c - ваше случайное значение
Кроме того, один раз из 2 ^ 64 вы выигрываете двойной джекпот и получаете неопределенное поведение деления на 0.
Если подумать, это никогда не бывает однообразным. Вы получите 0, если любое значение равно 0, что составляет почти 1 из 2 ^ 31, что намного больше, чем вы должны в двойном с 52 битами точности мантиссы.
Чтение из файлов является потокобезопасным AFAIK, поэтому использование fopen () для чтения из / dev / urandom даст «действительно случайные» байты.
Хотя могут быть потенциальные ошибки, я думаю, что любой набор таких байтов, доступ к которому осуществляется как целое число, деленное на максимальное целое число этого размера, даст значение с плавающей запятой между 0 и 1 с приблизительно таким распределением.
Например:
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
...
FILE* f = fopen("/dev/urandom", "r");
uint32_t i;
fread(&i, sizeof(i), 1, f); // check return value in real world code!!
fclose(f);
double theRandomValue = i / (double) (UINT32_MAX);
Несколько примечаний: (1) Вы не хотите, чтобы "- 1" после 2 ^ 32, так как phjr запросил исключить вывод 1.0. (2) В C++ нет **. Только << (используйте 1 << 32). (3) Вы, вероятно, захотите здесь беззнаковое int.
И вы не можете использовать int в качестве переменной, потому что это ключевое слово.
Хитрость в том, что вам нужен 54-битный рандомизатор, который соответствует вашим требованиям. Несколько строк кода с объединением, чтобы вставить эти 54 бита в мантиссу, и у вас есть свой номер. Хитрость не в двойном плавании, а в желаемом рандомизаторе.
Согласно java.util.Random вам нужно 53 бита, а не 54. Прочтите комментарии к методу nextDouble (), чтобы понять, почему. В противном случае вы точно на правильном пути.
Хотя подход Java не использует для этой цели объединение, он просто создает большое 53-битное число, а затем делит его на (1 << 53).
Это также должно избегать генерации денормализованных значений в 50% случаев, что и будет делать объединение. Ваш модуль с плавающей запятой и библиотеки могут корректно обрабатывать денормации, а могут и не работать.
Простой: Double имеет 52 бита точности, предполагая IEEE. Итак, сгенерируйте 52-битное (или больше) беззнаковое случайное целое число (например, путем чтения байтов из dev / urandom), преобразуйте его в double и разделите на 2 ^ (количество битов).
Это дает численно равномерное распределение (в том смысле, что вероятность того, что значение находится в заданном диапазоне, пропорциональна диапазону) вплоть до 52-й двоичной цифры.
Сложный: Однако в диапазоне [0,1) есть много двойных значений, которые выше не могут сгенерировать. Чтобы быть конкретным, половина значений в диапазоне [0,0.5) (те, у которых установлен младший бит) не может возникнуть. Три четверти значений в диапазоне [0,0.25) (те, у которых установлен один из двух наименьших битов) не могут встречаться и т. д., Возможно только одно положительное значение меньше 2 ^ -51, несмотря на то, что двойник способен представлять сквиллионы таких значений. Таким образом, нельзя сказать, что он действительно однороден в указанном диапазоне с полной точностью.
Конечно, мы не хотим выбирать один из этих удвоений с равной вероятностью, потому что тогда полученное число в среднем будет слишком маленьким. Нам все еще нужно, чтобы вероятность того, что результат находится в заданном диапазоне, была пропорциональна диапазону, но с более высокой точностью того, для каких диапазонов это работает.
У меня считать работает следующее. Я особо не изучал и не тестировал этот алгоритм (как вы, вероятно, можете сказать по тому, что там нет кода), и лично я бы не стал его использовать, не найдя надлежащих ссылок, указывающих на его действительность. Но вот идет:
Я не знаю, есть ли какое-то практическое применение у такого случайного двойника, заметьте. Ваше определение случайного числа должно в какой-то мере зависеть от того, для чего он нужен. Но если вы можете извлечь выгоду из того, что все 52 его значащих бита случайны, это действительно может быть полезно.
Я собирался не согласиться с вашим сложным подходом, но как только я прочитал все, это действительно имело смысл. Хотя я ни в чем не разбираюсь, поэтому на мое мнение не стоит рассчитывать. :-П
Я тоже - я считаю, что лучший подход - не разрабатывать алгоритмы, требующие «случайных чисел с плавающей запятой», если вы не уверены, что знаете, что это должно означать. Я не уверен, что знаю ...
Кажется, это неплохой способ:
unsigned short int r1, r2, r3;
// let r1, r2 and r3 hold random values
double result = ldexp(r1, -48) + ldexp(r2, -32) + ldexp(r3, -16);
Это основано на реализации Drand48 NetBSD.
Кто-нибудь знает, почему это дает единообразный результат в диапазоне 0,0 ... 1,0 с включительно 0,0 и исключительным 1,0? Если да, это действительно улучшило бы этот ответ, если бы его добавили. Редактирование, расширяющее это, было бы весьма признательно.
Чтобы уточнить, под униформой я подразумеваю отсутствие значительной предвзятости. Обычно это очень важно для большинства приложений, где уместно использовать / dev / urandom вместо PRNG, как задано в исходном вопросе.
/ dev / urandom не является POSIX и обычно недоступен.
Стандартный способ генерации двойного числа равномерно в [0,1) состоит в том, чтобы сгенерировать целое число в диапазоне [0,2 ^ N) и разделить его на 2 ^ N. Так что выберите свой любимый генератор случайных чисел и используйте его. Для моделирования я использую Мерсенн Твистер, так как он очень быстрый, но все еще плохо коррелирован. Фактически, он может сделать это за вас и даже имеет версию, которая обеспечивает большую точность для меньших чисел. Обычно вы даете ему начальное значение, которое помогает обеспечить повторяемость при отладке или показывать другим свои результаты. Конечно, вы можете заставить свой код получать случайное число из / dev / urandom в качестве начального числа, если оно не указано.
Для криптографических целей вы должны вместо этого использовать одну из стандартных криптографических библиотек, например openssl), которая действительно будет использовать / dev / urandom, когда это возможно.
Что касается безопасности потоков, большинство из них не будет работать, по крайней мере, со стандартными интерфейсами, поэтому вам нужно будет создать слой поверх или использовать их только в одном потоке. В тех, которые являются потокобезопасными, вы указываете состояние, которое они изменяют, так что вместо этого вы эффективно запускаете несколько невзаимодействующих генераторов случайных чисел, что может быть не тем, что вы ищете.
В этом есть ошибка - если a или b отрицательны, то при сравнении не обязательно выбирать значение с меньшей величиной в качестве числителя. Кроме того, я слишком тусклый, чтобы легко увидеть, что он дает равномерно распределенный вывод.