Выбор последовательности побитовых операций

Можно ли выбрать последовательность побитовых операций над случайно сгенерированными 64-битными числами так, чтобы биты в конечном результате имели заданную вероятность установки? Моя цель — написать функцию, которая принимает двойное значение в диапазоне [0, 1) и генерирует длинное число с битами в этой пропорции, используя только побитовые операции ИЛИ и И для случайно сгенерированных длинных значений.

Я думаю, что это сводится к минимизации ошибки |p - (4^n - 1)/(4^m)| где p — желаемое соотношение, m — общее количество операций, а n — количество из них, которые были OR. Это связано с тем, что результирующее соотношение после объединения k длинных элементов AND вместе составляет 1/(4^k), в то время как соотношение после объединения n длинных значений OR должно быть 1 - 1/4^n, поэтому соотношение после выполнения обоих действий равно (4 ^k-1)/(4^(k+n)). Однако здесь я застрял, так как не знаю, как найти лучшее значение k или n.

Не очень понятно, о чем вы спрашиваете. Вы имеете в виду, что вам нужна функция int f(double p), которая возвращает целое число, где каждый бит результата установлен с вероятностью p? (И что эти биты независимы? Потому что иначе все просто, мы просто возвращаем 0xffffffff с вероятностью p, иначе 0x0.)

Quuxplusone 27.03.2024 02:30

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

Andrew Kornder 27.03.2024 02:45
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
2
90
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если я правильно понял вашу постановку задачи, это очень похоже на русское крестьянское умножение. Учтите, что случайно сгенерированный long будет иметь каждый бит независимым и каждый бит установлен с вероятностью 50%. Предположим (ради рекурсии) у нас есть желаемая функция f(p); затем

rand()       gives the same distribution as f(0.5)
f(p) & f(q)  gives the same distribution as f(p*q)
f(p) | f(q)  gives the same distribution as f(p + q - p*q)

Итак, предположим, мы ищем f(0.9). Мы знаем это

f(0.9) = f(0.5) | f(x) where 0.5 + x - 0.5*x = 0.9, i.e. 0.5x = 0.4

Итак, теперь мы ищем f(0.8). Мы знаем это

f(0.8) = f(0.5) | f(x) where 0.5x = 0.3
f(0.6) = f(0.5) | f(x) where 0.5x = 0.1
f(0.2) = f(0.5) & f(x) where 0.5x = 0.2
f(0.4) = f(0.5) & f(x) where 0.5x = 0.4
f(0.8) = f(0.5) | f(x) where 0.5x = 0.3
[...]

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

f(0.9) = R | R | (R & R & (R | R | (R & R & ...)))

Например, этот код на C дает распределение, чертовски близкое к p=0,9:

#define R (rand() % 256)
unsigned get09() {
  unsigned x = R;  // 0.5
  x |= R; // 0.75      
  x |= R; // 0.875
  x &= R; // 0.4375     
  x &= R; // 0.21875
  x |= R; // 0.609375
  x |= R; // 0.8046875
  x |= R; // 0.90234375
  return x;
}

Теперь, могли бы вы стать еще ближе, используя продукцию, отличную от непосредственного использования R — например, можем ли мы использовать тот факт, что f(0.9) = f(0.684) | f(0.684), а затем искать способы производства f(0.684)? Конечно.

Возможно, это эффект Баадера-Мейнхофа в действии, но на самом деле (по счастливой случайности!) это похоже на проблему «кратчайшего пути» направленного гиперграфа, сродни https://mathoverflow.net/questions/466176/what-is -подходящее-имя-для-этой-самой-самой-проблемы-пути-в-бесконечном-крафте — см. https://quuxplusone.github.io/blog/2024/03/03/infinite-craft-theory / и Кнут Том 2 §4.6.3 «Оценка степеней» для некоторой теории. Мы ищем кратчайший путь от $V_0 = {0.5}$ до p, используя операцию $E$, которая на самом деле представляет собой две возможные операции: & и |. Таким образом, от {0,5} мы можем достичь любого из {0,5, 0,25, 0,75} за один шаг; мы можем достичь любого из {0,5, 0,25, 0,75, 0,125, 0,625, 0,375, 0,875, 0,1875, 0,8125} за два шага; и так далее; и вы просто хотите знать, сколько шагов пройдет, прежде чем мы достигнем некоторого числа в пределах определенного эпсилона p.


Обновлено: Мне пришло в голову, что эти цели гораздо лучше представлены в базе 2, а не в базе 10. Из {0,1} (база 2) мы можем достичь любого из {0,1, 0,01, 0,11} за один шаг; любой из {0,1, 0,01, 0,11, 0,001, 0,101, 0,011, 0,111, 0,0011, 0,1101} в два этапа; и так далее. Конечно, будет невозможно достичь любого p, двоичное представление которого имеет бесконечное количество 1-битов (например, p=0,9=0,1110011001...2); но мы можем подобраться сколь угодно близко.

И это действительно похоже на то, что общее решение состоит в том (как и в случае с русским крестьянским умножением) записать цель в двоичном формате; затем превратите 1 в |s, а 0 в &s. Например, чтобы получить p=0,7⏨, что равно 0,101100111...2, мы напишем это, где при чтении снизу вверх побитовые операции будут такими: |&||&&|||... Каждая побитовая операция с 0,510 добавляет к результату еще один ведущий бит. .

#define R (rand() % 256)
unsigned get07() {
  unsigned x = R | R;  // 0.11
  x |= R; // 0.111
  x &= R; // 0.0111    
  x &= R; // 0.00111
  x |= R; // 0.100111
  x |= R; // 0.1100111
  x &= R; // 0.01100111
  x |= R; // 0.101100111
  return x;
} 

Обратите внимание, что (как и в случае с цепочками сложения) метод русского крестьянина не всегда дает самое краткое решение. Например, чтобы получить 0,567510 = 0,10012 (Годболт):

unsigned russianPeasant() { return (R & R & R) | R; }
unsigned butILikeThis()   { return (R | R) & (R | R); }

Помните, что есть также ~f(x), который дает вам 1-f(x).

Frank Yellin 21.04.2024 22:39

@FrankYellin: В формулировке проблемы говорится «использование только побитовых операций ИЛИ и И», то есть не НЕ и не исключающее ИЛИ. Но я также думаю, что NOT и XOR на самом деле ничего не помогают минимизировать, потому что AND и OR имеют по существу зеркальный эффект. Например. (R | R) & (R | R) дает 0,5625, поэтому инвертирование каждой операции (R & R) | (R & R) дает 1 – 0,5625 = 0,4375. Вычисление первого, а затем НЕ его выполнение потребовало бы еще одной операции.

Quuxplusone 22.04.2024 01:11

Ты прав. Я пропустил часть об использовании только & и | в исходном вопросе. Хотя нет верхнего уровня, вероятно, не поможет, я верю, что использование его в середине сложного расчета дает вам что-то. Возможно, нет.

Frank Yellin 22.04.2024 02:12

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