Как преобразовать случайный uint64_t в случайное двойное значение в диапазоне (0, 1) с помощью побитовых операций

Лучшее, что я могу достичь, это этот (он дает результат от 0 до 0,5):

uint64_t const FRACTION_MASK = 0x1fffffffffffffull;
uint64_t const EXPONENT_BITS = 0x3fc0000000000000ull;
double to01(uint64_t i) {
    i = (i & FRACTION_MASK) | EXPONENT_BITS;
    return reinterpret_cast<double&>(i);
}

Я методом проб и ошибок вычисляю эти магические числа. Я даже не знаю, может ли он сгенерировать все значения, которые может повторять double от 0 до 0,5.

с C++ 11 это просто: std :: uniform_real_distribution

Marek R 03.09.2018 12:01

Битовые операции нетривиальны. Это потому, что каждое уменьшение значения вдвое порождает другой показатель степени. Таким образом, вам нужно будет отсканировать счетчик индекса самого высокого установленного бита и установить его как часть экспоненты, затем отбросить этот верхний установленный бит и повернуть оставшуюся часть как мантиссу. Альтернативный вариант - сгенерировать значение с плавающей запятой от 1,0 до 2,0], а затем вычесть 1,0. Раньше мы делали это, и это было немного быстрее, чем просто вводить значение и делить на (в то время) 65536.0.

Gem Taylor 03.09.2018 12:14

@JohnSmith Но ПОЧЕМУ? Создавать тонкие ошибки (перефразируйте ваш повтор: это может сделать любой неудачник)? Для меня это XY проблема.

Marek R 03.09.2018 12:15

@JohnSmith Обычно стандартный (std) спроектирован так, чтобы быть компактным и быстрым, однако вы можете добиться этого с помощью битового сдвига, и тогда xor будет в ярости (0,1)

M. Sol 03.09.2018 12:20

Преобразовать uint64_t в double просто, если вам все равно, что это за преобразование. Возврат 0.0 - хорошее начало. Предположительно, вы ищете чего-то более значимого, чем это; начните с того, что вы хотите, чтобы была взаимосвязь между входным и выходным значениями.

Pete Becker 03.09.2018 14:34

Для тех, кто голосует за закрытие, как это «слишком широко»? Мне это кажется довольно специфичным.

Galik 03.09.2018 14:55
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
281
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

Из-за способа кодирования двойных чисел сложно напрямую сгенерировать хорошее случайное число в диапазоне (0, 1), но легко получить одно в диапазоне [1, 2) [@GemTaylor].

double to_01(uint64_t i)
{
    constexpr uint64_t mask1 = 0x3FF0000000000000ULL;
    constexpr uint64_t mask2 = 0x3FFFFFFFFFFFFFFFULL;
    const uint64_t to_12 = (i | mask1) & mask2;
    double d;
    memcpy(&d, &to_12, 8);
    return d - 1;
}

Обратите внимание, что не все биты i используются для генерации результата. Также обратите внимание, что эта функция работает только для 64-битных чисел с плавающей запятой IEEE-754 [@geza].

Да, вам, вероятно, следует замаскировать ввод до 50 бит, чтобы обеспечить одинаковое качество представления для высоких и низких значений. С другой стороны, внутреннее 80-битное представление может сохранять разрешение во всем диапазоне.

Gem Taylor 03.09.2018 12:54

@GemTaylor, не могли бы вы объяснить маскировку до 50 бит? Вы предлагаете заменить mask2 на 0x3FFFFFFFFFFFFFFCULL? Почему это важно?

Evg 03.09.2018 13:19

Поэтому я предлагал вам очистить нижние 14 бит входного случайного значения, чтобы при преобразовании в двойное значение значения выше 0,5 сохранялись с тем же качеством, что и значения ниже 10 ^ -4. Но, честно говоря, я не думаю, что это имеет значение, так как они все равно будут усечены при преобразовании в диапазон (1.0–2.0) с точностью до 50 бит и будут работать так же на 63 битах, если вы сделали то же самое с длинным удваивается в 80-битном режиме. Когда они затем вычитаются до диапазона (0,0–1,0), все они сохранят это разрешение, так что фактически вы должны получить такое же распределение.

Gem Taylor 03.09.2018 15:26

Из того, что я собираю и судя только из вопроса, вы хотите сопоставить биты uint_64 с битами double так, чтобы ваш double находился между [0, 1]

двойное (64 бита) представление

-1S × (1.0 + 0.M) × 2E-смещение (смещение 1023)

Бит для знака S, 52 бита для матиссы и 11 бит для экспоненты.

Первые double подписаны, поэтому знаковый бит не может быть отображен, так как вы хотите, чтобы double> 0,0. Во-вторых, мы не можем превышать E-bias> 1022, иначе мы получим значение double больше 1.0.

поэтому маска для отображения uint_64 на удвоение между [0, 1] является

0 01111111110 111111111111111111111111111111111111111111111111 в шестнадцатеричном формате 0x3FEFFFFFFFFFFFFFull

поэтому код

uint64_t const MASK = 0x3FEFFFFFFFFFFFFFull;
double to01(uint64_t i) {
    i = (i & MASK);
    return reinterpret_cast<double&>(i);
}

должен сделать свое дело. И с помощью этого метода вы использовали 61 бит из 64 бит вашего uint_64.

Но будьте осторожны: если ваш генератор uint_64 однороден по [0, uint64_t_max], генератор, полученный с помощью to01, не будет однородным по [0,1]!

очень будет неравномерным: 3.65484e-229, 1.42602e-116, 4.34558e-152, 1.61803e-107, ... Лучше не трогать экспоненциальную часть двойника.

Evg 03.09.2018 15:47

Однако он отвечает на вопрос. Не так ли? Если он хочет, чтобы он был единообразным, он должен переформулировать и быть готовым использовать только 52 бита, а не 61 из 64.

PilouPili 03.09.2018 15:52

Оно делает. Мы не знаем, какой вариант использования имеет в виду OP, но я с трудом могу вообразить такой, в котором такое крайне неоднородное распределение было бы приемлемым.

Evg 03.09.2018 16:07

Это то, что я использую Я сдвигаюсь на 12, чтобы получить 52 бита I или с 1, чтобы я получал только нечетные значения, это позволяет избежать возврата 0 Затем вычтите 1,0 при возврате

Если ваш источник целых чисел равномерно распределен Ваши результаты будут равномерно распределены

double convert_uint64_to_double(uint64_t value)
{
    uint64_t u64 = 0x3FF0000000000000ULL | ((value >> 12) | 1) ;
    return *(double*)&u64 - 1.0;
}

double convert_uint32_to_double(uint32_t value)
{
    uint64_t u64 = 0x3FF0000000000000ULL | (((uint64_t(value) << 1) | 1) << 19);
    return *(double*)&u64 - 1.0;
}

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