std::uniform_real_distribution
.
У S.T.L. есть really good talk. начиная с конференции Going Native в этом году, которая объясняет, почему вы должны использовать стандартные дистрибутивы, когда это возможно. Короче говоря, ручной код имеет тенденцию быть смехотворно низкого качества (думаю, std::rand() % 100
) или иметь более тонкие недостатки однородности, например, в (std::rand() * 1.0/RAND_MAX) * 99
, что является примером, приведенным в разговоре, и является частным случаем кода, опубликованного в вопрос.
EDIT: Я посмотрел на libstdC++ реализации 's из std::uniform_real_distribution
, и это то, что я нашел:
Реализация производит ряд в диапазоне [dist_min, dist_max)
, используя простое линейное преобразование из некоторого числа, полученного в диапазоне [0, 1)
, Он генерирует этот номер источника, используя std::generate_canonical
, the implementation of which my be found here (в конце файла). std::generate_canonical
определяет количество раз (обозначенное как k
) диапазон распределения, выраженный как целое число и обозначаемый здесь как r
*, будет вписываться в мантиссу целевого типа. То, что он тогда делает, состоит в том, чтобы генерировать одно число в [0, r)
для каждого сегмента r
-сегмента мантиссы и, используя арифметику, заполнять каждый сегмент соответственно.Формула для результирующего значения может быть выражена как
Σ(i=0, k-1, X/(r^i))
, где X
является случайной величиной в [0, r)
. Каждое деление на диапазон эквивалентно сдвигу на количество бит, используемых для его представления (то есть, log2(r)
), и таким образом заполняет соответствующий сегмент мантиссы. Таким образом, используется вся точность целевого типа, и поскольку диапазон результатов равен [0, 1)
, показатель остается 0
** (по модулю смещения), и вы не получаете проблем с однородностью, которые у вас возникают, когда вы начинаете беспорядок с показателем.
Я не буду верить, что этот метод криптографически безопасен (и у меня есть подозрения о возможных ошибках «один за другим» при вычислении размера r
), но я полагаю, что он значительно более надежный с точки зрения единообразия чем вы делали Boost, и определенно лучше, чем возиться с std::rand
.
Может быть, стоит отметить, что код подталкивания фактически является вырожденный случай этого алгоритма, где k = 1
, а это означает, что она эквивалентна , если диапазон входного требует, по меньшей мере, 23 бита, чтобы представить его размер (ПЭО 754 одно- точность) или не менее 52 бит (с двойной точностью). Это означает минимальный диапазон ~ 8,4 миллиона или ~ 4,5e15, соответственно. В свете этой информации, я не думаю, что если вы используете двоичный генератор, то реализация Boost - это , а собирается разрезать ее.
После краткого обзора libc++’s implementation, похоже, что они используют один и тот же алгоритм, реализованный несколько иначе.
(*) r
- это фактически диапазон ввода плюс один. Это позволяет использовать значение ёртга max
в качестве допустимого ввода.
(**) Строго говоря, кодированный показатель не является 0
, так как IEEE 754 кодирует неявное начало 1 перед основанием знака. Концептуально, однако, это не имеет отношения к этому алгоритму.
Я предполагаю, что «возьмите бесконечно точное равномерное генератор случайных чисел на диапазоне' [min, max) '", потому что без ограничений диапазона не существует равномерных генераторов случайных чисел. :) – Yakk
@Yakk Это создало бы неправильное распределение на самых концах из-за округления, '[min - n * ULP, max + n * ULP)' было бы хорошо, хотя для некоторого значения 'n', что я слишком ленив, чтобы подумать, но, вероятно, это 1. – orlp
@nightcracker Чтобы убедиться, что я правильно понимаю: вы хотите использовать dsitrbution, где некоторые битовые шаблоны с плавающей запятой гораздо более распространены, чем другие (из-за того, что они находятся дальше от 0), НЕ где каждая битовая диаграмма с плавающей запятой равновероятна? –