равномерно распределенное несмещенное 4-битное экономное отображение диапазона с немного ограниченным TRNG - PullRequest
1 голос
/ 22 марта 2020

Я пытаюсь реализовать отображение диапазона для выходных файлов TRNG для приложения C с диапазонами до 4 бит в размере. Из-за проблемы смещения ямы я остановился на использовании алгоритма сброса.

Моя идея экономного алгоритма будет выглядеть примерно так:

- Считать 16 байтов из файла и сохранить как индексированный Целочисленное битовое поле без знака длиной 128 битов, которое должно быть выбрано с помощью битовой маски, n битов за раз.
- Предварительно определите максимально возможные диапазоны / сегменты, необходимые для каждого входа, и сохраните их в массиве.
- Для каждого n битов в bitbucket выбирает вход из массива, который не будет отбрасывать его, если он существует. Если 2 бита не могут найти вход, попробуйте 3 бита, а если он не может найти вход, попробуйте 4 бита. Сначала, когда имеется много входов, их легко не выбросить, но по мере того, как выбор входов становится меньше, сбросы станут более распространенными. Я не совсем уверен, лучше ли начинать с меньшего количества битов и идти вверх или делать наоборот.

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

Существующие реализации / алгоритмы: Этот выглядит как пример более сложного и эффективного метода о скупом картографировании диапазона, но я нахожу его объяснение совершенно непроницаемым. Может ли кто-нибудь объяснить мне это по-английски sh или предложить книгу, которую я мог бы прочитать, или университетский курс, который я мог бы взять, который дал бы мне представление, чтобы понять это?

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

Основная идея arc4random кажется таковой, пока число голубей (max_randvalue_output) равно Равномерно делится на число отверстий (rangeupperbound), сама функция по модулю представляет собой элегантный и несмещенный преобразователь диапазона. Однако, по модулю, кажется, имеет значение только тогда, когда вы не потягиваете биты, то есть когда выходной сигнал из случайного источника превышает биты ceil (log2 (buckets)).

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

Так что я планирую просто написать две реализации: немного потягивающий экономный тип преобразователя диапазона, который может или не может быть немного похож на пример mathforum (который я не понимаю), и инвариантный преобразователь входных данных по модулю байтов, который принимает байтовые входы от TRNG и использует метод отбрасывания от верха самого большого множества по модулю debiasing для сопоставления (x) n голубей с n отверстиями, который должен быть похож на arc4random. Когда я закончу, я планирую опубликовать их в обзоре кода.

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

Ответы [ 2 ]

2 голосов
/ 22 марта 2020

Я посмотрел на «Fast Dice Roller» (FDR), на который указывает @ Peter.O , который действительно прост (и избегает деления). Но каждый раз, когда генерируется случайное число, оно потребляет некоторое количество битов и отбрасывает часть тех битов, которые не использует.

Методы «пакетирования» / «объединения», кажется, работают лучше, чем FDR, потому что неиспользуемые доли битов (хотя бы частично) сохраняются.

Но, что интересно, вещь, на которую вы ссылаетесь DrMath , в основном такая же, как FDR, но не начинается с нуля для каждой случайное значение, которое он возвращает.

Таким образом, FDR для возврата 0..n-1 идет:

  random(n):
    m = 1 ; r = 0 
    while 1:
        # Have r random and evenly distributed in 0..m-1
        # Need m >= n -- can double m and double r adding random bit until
        #                we get that.  r remains evenly distributed in 0..m-1 
        while m < n: r = 2*r + next_bit() ; m = m*2
        # Now have r < m and n <= m < n*2
        if r < n: return r   # Hurrah !
        # Have overshot, so reduce m and r to m MOD n and r MOD m
        m -= n ; r -= n ;

В DrMath идет речь:

  # Initialisation once before first call of random(m)
  ms = 1 ; rs = 0
  N = ... # N >= maximum n and N*2 does not overflow 

  # The function -- using the "static"/"global" ms, rs and N 
  random(n):
    m = ms ; r = rs
    while 1:
        # Same as FDR -- except work up to N not n
        while m < N: r = 2*r + next_bit() ; m = m*2 ;
        # Now have r < m and m >= N
        # Set nq = largest multiple of n <= m
        # In FDR, at this point q = 1 and nq = n
        q  = m DIV n ;
        nq = n * q
        if r < nq:             # all set if r < nq
            # in FDR ms = 1, rs = 0 
            ms = q             # keep stuff not used this time
            rs = r DIV n       # ditto
            return r MOD n     # hurrah !
        # Overshot, so reduce MOD n*q -- remembering, for FDR q == 1
        m = m - nq 
        r = r - nq

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

При тестировании я обнаружил:

  FDR:    for 100000 values range=3 used 266804 bits cost=1.6833
  DrMath: for 100000 values range=3 used 158526 bits cost=1.0002

Где cost равно bits-used / (100000 * log2(3)) отмечая, что log2 (3) = (1,58496). (Таким образом, cost - это количество используемых битов, деленное на количество битов, которые можно использовать).

Также:

  FDR:    for 100000 values range=17: 576579 bits cost=1.4106
  DrMath: for 100000 values range=17: 408774 bits cost=1.0001

И:

  FDR:    for 100000 values ranges=5..60: 578397 bits cost=1.2102
  DrMath: for 100000 values ranges=5..60: 477953 bits cost=1.0001

, где построено 100000 значений, и для каждого из них выбран диапазон в 5..60 (включительно).

Мне кажется, что DrMath имеет его! Хотя для больших диапазонов у него меньше преимуществ.

Имейте в виду ... DrMath использует по крайней мере 2 деления на случайное возвращаемое значение, что дает мне умения во время выполнения. Но вы сказали, что вас не интересует эффективность во время выполнения.


Как это работает?

Итак, мы хотим, чтобы последовательность случайных значений r была равномерно распространяется в диапазоне 0..n-1. Неудобно, что у нас есть только источник случайности, который дает нам случайные значения, которые равномерно распределены в 0..m-1. Обычно m будет степенью 2 - и предположим, что n < m (если n == m, проблема тривиальна, если n > m, проблема невозможна). Для любого r мы можем взять r MOD n, чтобы получить случайное значение в требуемом диапазоне. Если мы используем r только тогда, когда r < n, тогда (тривиально) мы получаем равномерное распределение, которое мы хотим. Если мы используем r только тогда, когда r < (n * q) и (n * q) < m, у нас также есть равномерное распределение. Мы здесь «отвергаем» r, которые являются «слишком большими». Чем меньше r мы отвергаем, тем лучше. Поэтому мы должны выбрать q так, чтобы (n * q) <= m < (n * (q-1)) - так, чтобы n * q было наибольшим кратным n, меньшим или равным m. Это, в свою очередь, говорит нам о том, что n «намного меньше», чем m, следует отдавать предпочтение.

Когда мы «отвергаем» данный r, мы можем выбросить все это, но это поворачивает не быть полностью необходимым. Кроме того, m не обязательно должно быть степенью 2. Но мы вернемся к этому позже.

Вот некоторые рабочие Python:

M = 1
R = 0
N = (2**63)    # N >= maximum range

REJECT_COUNT = 0

def random_drmath(n):
    global M, R, REJECT_COUNT

    # (1) load m and r "pool"
    m = M
    r = R
    while 1:
        # (2) want N <= m < N*2
        #     have 0 <= r < m, and that remains true.
        #     also r uniformly distributed in 0..m-1, and that remains true
        while m < N:
            r = 2*r + next_bit()
            m = m*2

        # (3) need r < nq where nq = largest multiple of n <= m
        q  = m // n
        nq = n * q
        if r < nq:
            # (4) update the m and r "pool" and return 0..n-1 
            M = q
            R = r // n
            return r % n       # hurrah !

        # (5) reject: so reduce both m and r by MOD n*q
        m = m - nq 
        r = r - nq
        REJECT_COUNT += 1

Должно иметь N> = максимальная дальность, желательно намного больше. 2**31 или 2**63 являются очевидными вариантами.

При первом вызове random_drmath() step (2) будет читать случайные биты, чтобы «заполнить пул». Для N = 2**63, получится m = 2**63 и r с 63 случайными битами. Ясно, что r является случайным и равномерно распределен в 0..m-1. [Пока все хорошо.]

Теперь (и при всех последующих вызовах random_drmath()) мы надеемся равномерно извлечь случайное значение в 0..n-1 из r, как обсуждалось выше. Итак - step (3) - создает nq, который является наибольшим кратным n, которое меньше или равно m. Если r >= nq мы не можем использовать его, потому что в nq..m-1 меньше n значений - это обычный критерий «отклонения».

Итак, где r < nq может возвращать значение - - шаг (4). Хитрость в том, чтобы думать о m и r как о числах "base-n". Ls "di git" из r извлекается (r % n) и возвращается. Затем m и r сдвигаются вправо на один «ди git» (q = m // n и r // n) и сохраняются в «пуле». Я думаю, что ясно, что на данный момент r и m все еще r < m и r случайны и равномерно распределены в 0..m-1. Но m больше не является степенью 2 - но это нормально.

Но, если r >= nq должен уменьшить r и m вместе - шаг (5) - и попробуйте снова. Тривиально, можно установить m = 1 ; r = 0 и начать заново. Но то, что мы делаем, вычитаем nq из m и r, что оставляет r равномерно распределенным в 0..m-1. Этот последний шаг выглядит как волшебный c, но мы знаем, что r находится в nq..m-1, и каждое возможное значение имеет равную вероятность, поэтому r-nq находится в диапазоне 0..m-nq-1, и каждое возможное значение все еще имеет равную вероятность! [Помните, что «инвариант» в верхней части while l oop означает, что r является случайным и равномерно распределен в 0..m-1.]

Для малых n шаг отклонения будет отбросить большую часть r, но для небольших n (по сравнению с N) мы надеемся не слишком часто отказываться. И наоборот, для больших n (по сравнению с N) мы можем ожидать отклонения чаще, но при этом сохраняются, по крайней мере, некоторые из случайных битов, которые мы съели до сих пор. Я чувствую, что может быть способ сохранить больше r ... но я не думал о простом способе сделать это ... и если стоимость чтения одного случайного бита высока, возможно, стоит попробовать чтобы найти непростой способ!

FWIW: установка N = 128 Я получаю:

  FDR:    for 100000 values ranges=3.. 15: 389026 bits cost=1.2881
  DrMath: for 100000 values ranges=3.. 15: 315815 bits cost=1.0457

  FDR:    for 100000 values ranges 3.. 31: 476428 bits cost=1.2371
  DrMath: for 100000 values ranges 3.. 31: 410195 bits cost=1.0651

  FDR:    for 100000 values ranges 3.. 63: 568687 bits cost=1.2003
  DrMath: for 100000 values ranges 3.. 63: 517674 bits cost=1.0927

  FDR:    for 100000 values ranges 3..127: 664333 bits cost=1.1727
  DrMath: for 100000 values ranges 3..127: 639269 bits cost=1.1284

, так как n приближается к N стоимость за единицу увеличивается.

2 голосов
/ 22 марта 2020

Существует гораздо более простой подход к генерации случайных чисел в диапазоне из случайного потока битов, который не только оптимально эффективен, но и точен. Этот метод Дж. Лумброзо называется «быстрый барабан для игры в кости»:

" Оптимальная дискретная равномерная генерация по броскам монет и приложениям ", 2013.

См. Также этот вопрос .

...