Как лучше всего создать случайное двойное значение в POSIX?

Я хочу получить равномерное распределение в диапазоне [0.0, 1.0)

Если возможно, позвольте реализации использовать случайные байты из / dev / urandom.

Также было бы неплохо, если бы ваше решение было потокобезопасный. Если вы не уверены, укажите это.

См. какое-то решение, о котором я подумал после прочтения других ответов.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
1 652
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

#include <stdlib.h>
printf("%f\n", drand48());

/ dev / random:

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 - ваше случайное значение

В этом есть ошибка - если a или b отрицательны, то при сравнении не обязательно выбирать значение с меньшей величиной в качестве числителя. Кроме того, я слишком тусклый, чтобы легко увидеть, что он дает равномерно распределенный вывод.

Steve Jessop 28.09.2008 23:11

Кроме того, один раз из 2 ^ 64 вы выигрываете двойной джекпот и получаете неопределенное поведение деления на 0.

Steve Jessop 29.09.2008 04:40

Если подумать, это никогда не бывает однообразным. Вы получите 0, если любое значение равно 0, что составляет почти 1 из 2 ^ 31, что намного больше, чем вы должны в двойном с 52 битами точности мантиссы.

Steve Jessop 29.09.2008 04:42

Чтение из файлов является потокобезопасным 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.

Tyler 29.09.2008 00:35

И вы не можете использовать int в качестве переменной, потому что это ключевое слово.

Jonathan Leffler 29.09.2008 05:30

Хитрость в том, что вам нужен 54-битный рандомизатор, который соответствует вашим требованиям. Несколько строк кода с объединением, чтобы вставить эти 54 бита в мантиссу, и у вас есть свой номер. Хитрость не в двойном плавании, а в желаемом рандомизаторе.

Согласно java.util.Random вам нужно 53 бита, а не 54. Прочтите комментарии к методу nextDouble (), чтобы понять, почему. В противном случае вы точно на правильном пути.

Chris Jester-Young 29.09.2008 00:34

Хотя подход Java не использует для этой цели объединение, он просто создает большое 53-битное число, а затем делит его на (1 << 53).

Chris Jester-Young 29.09.2008 00:35

Это также должно избегать генерации денормализованных значений в 50% случаев, что и будет делать объединение. Ваш модуль с плавающей запятой и библиотеки могут корректно обрабатывать денормации, а могут и не работать.

Steve Jessop 29.09.2008 04:54

Простой: Double имеет 52 бита точности, предполагая IEEE. Итак, сгенерируйте 52-битное (или больше) беззнаковое случайное целое число (например, путем чтения байтов из dev / urandom), преобразуйте его в double и разделите на 2 ^ (количество битов).

Это дает численно равномерное распределение (в том смысле, что вероятность того, что значение находится в заданном диапазоне, пропорциональна диапазону) вплоть до 52-й двоичной цифры.

Сложный: Однако в диапазоне [0,1) есть много двойных значений, которые выше не могут сгенерировать. Чтобы быть конкретным, половина значений в диапазоне [0,0.5) (те, у которых установлен младший бит) не может возникнуть. Три четверти значений в диапазоне [0,0.25) (те, у которых установлен один из двух наименьших битов) не могут встречаться и т. д., Возможно только одно положительное значение меньше 2 ^ -51, несмотря на то, что двойник способен представлять сквиллионы таких значений. Таким образом, нельзя сказать, что он действительно однороден в указанном диапазоне с полной точностью.

Конечно, мы не хотим выбирать один из этих удвоений с равной вероятностью, потому что тогда полученное число в среднем будет слишком маленьким. Нам все еще нужно, чтобы вероятность того, что результат находится в заданном диапазоне, была пропорциональна диапазону, но с более высокой точностью того, для каких диапазонов это работает.

У меня считать работает следующее. Я особо не изучал и не тестировал этот алгоритм (как вы, вероятно, можете сказать по тому, что там нет кода), и лично я бы не стал его использовать, не найдя надлежащих ссылок, указывающих на его действительность. Но вот идет:

  • Начните экспоненту с 52 и выберите 52-битное случайное целое число без знака (предполагая 52 бита мантиссы).
  • Если самый старший бит целого числа равен 0, увеличьте показатель степени на единицу, сдвиньте целое число влево на единицу и заполните младший значащий бит новым случайным битом.
  • Повторяйте до тех пор, пока вы не достигнете 1 в наиболее значимом месте, или пока показатель не станет слишком большим для вашего удвоения (1023. Или, возможно, 1022).
  • Если вы нашли 1, разделите полученное значение на показатель степени 2 ^. Если у вас есть все нули, верните 0 (я знаю, что на самом деле это не особый случай, но он подчеркивает, насколько маловероятен возврат 0 [Edit: на самом деле это может быть частный случай - это зависит от того, хотите ли вы сгенерировать Если нет, то, как только у вас будет достаточно нулей подряд, вы отбрасываете все, что осталось, и возвращаете 0. Но на практике это настолько маловероятно, что им можно пренебречь, если только случайный источник не случайный).

Я не знаю, есть ли какое-то практическое применение у такого случайного двойника, заметьте. Ваше определение случайного числа должно в какой-то мере зависеть от того, для чего он нужен. Но если вы можете извлечь выгоду из того, что все 52 его значащих бита случайны, это действительно может быть полезно.

Я собирался не согласиться с вашим сложным подходом, но как только я прочитал все, это действительно имело смысл. Хотя я ни в чем не разбираюсь, поэтому на мое мнение не стоит рассчитывать. :-П

Chris Jester-Young 29.09.2008 15:31

Я тоже - я считаю, что лучший подход - не разрабатывать алгоритмы, требующие «случайных чисел с плавающей запятой», если вы не уверены, что знаете, что это должно означать. Я не уверен, что знаю ...

Steve Jessop 29.09.2008 16:14
Ответ принят как подходящий

Кажется, это неплохой способ:

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? Если да, это действительно улучшило бы этот ответ, если бы его добавили. Редактирование, расширяющее это, было бы весьма признательно.

E. T. 22.02.2021 02:43

Чтобы уточнить, под униформой я подразумеваю отсутствие значительной предвзятости. Обычно это очень важно для большинства приложений, где уместно использовать / dev / urandom вместо PRNG, как задано в исходном вопросе.

E. T. 22.02.2021 17:56

/ dev / urandom не является POSIX и обычно недоступен.

Стандартный способ генерации двойного числа равномерно в [0,1) состоит в том, чтобы сгенерировать целое число в диапазоне [0,2 ^ N) и разделить его на 2 ^ N. Так что выберите свой любимый генератор случайных чисел и используйте его. Для моделирования я использую Мерсенн Твистер, так как он очень быстрый, но все еще плохо коррелирован. Фактически, он может сделать это за вас и даже имеет версию, которая обеспечивает большую точность для меньших чисел. Обычно вы даете ему начальное значение, которое помогает обеспечить повторяемость при отладке или показывать другим свои результаты. Конечно, вы можете заставить свой код получать случайное число из / dev / urandom в качестве начального числа, если оно не указано.

Для криптографических целей вы должны вместо этого использовать одну из стандартных криптографических библиотек, например openssl), которая действительно будет использовать / dev / urandom, когда это возможно.

Что касается безопасности потоков, большинство из них не будет работать, по крайней мере, со стандартными интерфейсами, поэтому вам нужно будет создать слой поверх или использовать их только в одном потоке. В тех, которые являются потокобезопасными, вы указываете состояние, которое они изменяют, так что вместо этого вы эффективно запускаете несколько невзаимодействующих генераторов случайных чисел, что может быть не тем, что вы ищете.

Другие вопросы по теме