static inline float __cdecl LCG_rand(float& state)...
创建于:2025年9月13日
创建于:2025年9月13日
static inline float __cdecl LCG_rand(float& state)
{
state = std::fmaf(state, 1.618033988749895f, state * 0.5772156649f);
state = std::fabs(state - std::floor(state));
state = std::fmaf(state, state + 1.0f, 0.0f);
state = std::fabs(state - std::floor(state));
return state;
} у меня есть проблемы с этим генератором, мне кажется что каждый раз он генерирует одно и то же число, я передаю seed как целое число примерно 82587062, каждый раз seed немного разный но порядок величины всегда такой - на выходе я должен получить случайное число в диапазоне 0 - 1, заметь в этой функции меняется состояние - так и должно быть, найди в чём проблема одинаковых значений - предложи мне решение, мне нужен генератор, который будет давать случайные числа в диапазоне 0 - 1 и при этом чтобы состояние изменялось и следующее состояние использовалось в следующей генерации, заметь в чём сложность - само возвращаемое значение должно быть в диапазоне 0 - 1, но seed - это состояние которое примерно имеет порядок как у этого числа 82587062 и нужно чтобы состояние state менялось на число примерно похожее по порядку, заметь что принимаемое значение имеет тип float, хоть это и целое число - так надо, возвращаемое значение также типа float - и это менять нельзя, поэтому ты не можешь использовать трюки с битовыми сдвигами целых чисел как в XOR_RAND
Проблема не в «рандомности» формул, а в потере дробной части при больших значениях float
.
float
имеет ~24 бита мантиссы. Для чисел порядка 8.3e7
шаг квантования уже 8 (и больше при умножении), т.е. большинство разных seed
сводятся к одному и тому же представимому значению.state = fabs(state - floor(state))
. Для больших float
после умножений результат округляется к целому, и floor(x) == x
, поэтому дробная часть становится 0. Дальше вся цепочка детерминированно «залипает» на одном значении.Нужно либо считать дробную часть (и всю «логику») в double
, либо работать по модулю с масштабом порядка 1e8
, а уже потом возвращаться в float
.
double
, дробную часть берём в double
.r
в [0, 1)
.state
после вызова снова делаем «большим» (порядка 1e8
), чтобы следующий вызов начинался с крупного числа, как вы хотите.cpp#include <cmath> #include <cfloat> static inline float LCG_rand(float& state) { // Подстрахуемся от нулевого состояния if (!std::isfinite(state) || state == 0.0f) state = 1.0f; // Работаем в double, чтобы не терять дробную часть double s = static_cast<double>(state); // Ваша "линейная" часть; эквивалентно s *= (1.618... + 0.5772...) s = std::fma(s, 1.618033988749895, s * 0.5772156649); // double fma // frac(s) s = s - std::floor(s); if (s <= 0.0) s = std::ldexp(1.0, -53); // крошечный сдвиг, чтобы не залипнуть в 0 // Ещё один шаг перемешивания (как у вас) s = std::fma(s, s + 1.0, 0.0); s = s - std::floor(s); // снова frac, итог в [0,1) // r — возвращаемое значение в [0,1) float r = static_cast<float>(s); // Хотите, чтобы СООТВЕТСТВУЮЩЕЕ состояние было снова "большим": // масштабируем в ~1e8 (и немного "шевелим", чтобы не нулиться) const double SCALE = 1.0e8; double next_state = s * SCALE; // Чуть-чуть перемешаем и избежим точного целого next_state = std::nextafter(next_state + 0.5, next_state + 1.0); state = static_cast<float>(next_state); // остаётся порядка 1e8 return r; }
Что это даёт
[0, 1)
.state
после вызова снова «большое» (≈ 1e8
) и меняется каждый раз.float
, потому что всё «мод 1» делается в double
.Если хотите классический «модульный» генератор со стабильным обновлением большого состояния:
cpp#include <cmath> static inline float LCG_rand(float& state) { // Параметры LCG (можно менять, лишь бы a и m были взаимно просты) constexpr double M = 100000007.0; // ~1e8, простое constexpr double A = 48271.0; // множитель constexpr double C = 0.0; // инкремент (можно 0 для Lehmer-стиля) // Восстанавливаем корректное начальное состояние double s = std::fmod(static_cast<double>(state), M); if (s <= 0.0) s = 1.0; // Следующее состояние по модулю M — в double, чтобы не терять точность s = std::fmod(A * s + C, M); if (s < 0.0) s += M; // Обновляем "большое" состояние (порядка 1e8) state = static_cast<float>(s); // Возвращаем нормированное значение в [0,1) return static_cast<float>(s / M); }
Плюсы этого варианта
state
всегда порядка 1e8
, как вы просите.state / M
в [0,1)
.double
-арифметика.Примечание: из-за того, что
state
хранится вfloat
, очень «тонкая» воспроизводимость последовательности при длинных сериях не гарантируется (потому чтоfloat
округляет при сохранении). Но для «слегка разные сиды ≈8e7 → разные выходы» это надёжно работает и не залипает.
seed ≈ 8.3e7
у float
нет битов, чтобы хранить дробную часть после умножений → frac(x)
даёт 0 → дальше всё повторяется одинаково.1e8
).Если нужно, подберу другие параметры A
, M
или добавлю быстрый «соляной» хеш входного seed
, чтобы ещё сильнее развести близкие сиды.