Лучшее, что я могу достичь, это этот (он дает результат от 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.
Битовые операции нетривиальны. Это потому, что каждое уменьшение значения вдвое порождает другой показатель степени. Таким образом, вам нужно будет отсканировать счетчик индекса самого высокого установленного бита и установить его как часть экспоненты, затем отбросить этот верхний установленный бит и повернуть оставшуюся часть как мантиссу. Альтернативный вариант - сгенерировать значение с плавающей запятой от 1,0 до 2,0], а затем вычесть 1,0. Раньше мы делали это, и это было немного быстрее, чем просто вводить значение и делить на (в то время) 65536.0.
@JohnSmith Но ПОЧЕМУ? Создавать тонкие ошибки (перефразируйте ваш повтор: это может сделать любой неудачник)? Для меня это XY проблема.
@JohnSmith Обычно стандартный (std) спроектирован так, чтобы быть компактным и быстрым, однако вы можете добиться этого с помощью битового сдвига, и тогда xor будет в ярости (0,1)
Преобразовать uint64_t в double просто, если вам все равно, что это за преобразование. Возврат 0.0 - хорошее начало. Предположительно, вы ищете чего-то более значимого, чем это; начните с того, что вы хотите, чтобы была взаимосвязь между входным и выходным значениями.
Для тех, кто голосует за закрытие, как это «слишком широко»? Мне это кажется довольно специфичным.





Из-за способа кодирования двойных чисел сложно напрямую сгенерировать хорошее случайное число в диапазоне (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-битное представление может сохранять разрешение во всем диапазоне.
@GemTaylor, не могли бы вы объяснить маскировку до 50 бит? Вы предлагаете заменить mask2 на 0x3FFFFFFFFFFFFFFCULL? Почему это важно?
Поэтому я предлагал вам очистить нижние 14 бит входного случайного значения, чтобы при преобразовании в двойное значение значения выше 0,5 сохранялись с тем же качеством, что и значения ниже 10 ^ -4. Но, честно говоря, я не думаю, что это имеет значение, так как они все равно будут усечены при преобразовании в диапазон (1.0–2.0) с точностью до 50 бит и будут работать так же на 63 битах, если вы сделали то же самое с длинным удваивается в 80-битном режиме. Когда они затем вычитаются до диапазона (0,0–1,0), все они сохранят это разрешение, так что фактически вы должны получить такое же распределение.
Из того, что я собираю и судя только из вопроса, вы хотите сопоставить биты 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, ... Лучше не трогать экспоненциальную часть двойника.
Однако он отвечает на вопрос. Не так ли? Если он хочет, чтобы он был единообразным, он должен переформулировать и быть готовым использовать только 52 бита, а не 61 из 64.
Оно делает. Мы не знаем, какой вариант использования имеет в виду OP, но я с трудом могу вообразить такой, в котором такое крайне неоднородное распределение было бы приемлемым.
Это то, что я использую Я сдвигаюсь на 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;
}
с C++ 11 это просто: std :: uniform_real_distribution