Вопрос

Это, вероятно, вопрос для эксперта x86 FPU:

Я пытаюсь написать функцию, которая генерирует случайное значение с плавающей запятой в диапазоне [min, max]. Проблема в том, что мой алгоритм генератора (Mersenne Twister с плавающей точкой, если вам любопытно) возвращает только значения в диапазоне [1,2) - то есть я хочу включить верхнюю границу, но мой " источник " сгенерированное значение из исключительной верхней границы. Подвох в том, что базовый генератор возвращает 8-байтовое двойное число, но мне нужно только 4-байтовое число с плавающей запятой, и я использую режим округления FPU по умолчанию - Nearest.

Я хочу знать, приведет ли само усечение в этом случае к тому, что мое возвращаемое значение будет включать в себя значение max, когда внутреннее 80-битное значение FPU достаточно близко, или я должен увеличить значение моего максимального значения до того, как умножение его на промежуточное случайное число в [1,2), или я должен изменить режимы FPU. Или любые другие идеи, конечно.

Вот код, который я сейчас использую, и я проверил, что 1.0f преобразуется в 0x3f800000:

float MersenneFloat( float min, float max )
{
    //genrand returns a double in [1,2)
    const float random = (float)genrand_close1_open2(); 
    //return in desired range
    return min + ( random - 1.0f ) * (max - min);
}

Если это имеет значение, это должно работать как на Win32 MSVC ++, так и на Linux gcc. Кроме того, использование каких-либо версий оптимизации SSE изменит ответ на этот вопрос?

Редактировать . Ответ - да, в этом случае усечение от двойного до плавающего достаточно, чтобы результат включал макс. Подробнее смотрите в ответе Crashworks.

Это было полезно?

Решение

Операции SSE будут тонко изменять поведение этого алгоритма, потому что они не имеют промежуточного 80-битного представления - математика действительно выполняется в 32 или 64 битах. Хорошей новостью является то, что вы можете легко протестировать его и посмотреть, изменит ли он ваши результаты, просто указав параметр / ARCH: SSE2 в командной строке для MSVC, что заставит его использовать скалярные операции SSE вместо команд FPU x87 для обычной плавающей запятой математика.

Я не уверен, насколько точно точное поведение округления происходит вокруг целочисленных границ, но вы можете проверить, что произойдет, когда 1.999 .. округляется с 64 до 32 бит на например,

static uint64 OnePointNineRepeating = 0x3FF FFFFF FFFF FFFF // exponent 0 (biased to 1023), all 1 bits in mantissa
double asDouble = *(double *)(&OnePointNineRepeating);
float asFloat = asDouble;
return asFloat;

Правка, результат: оригинальный автор выполнил этот тест и обнаружил, что с усечением 1.99999 округляется до 2 как с / arch: SSE2, так и без него.

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

Если вы отрегулируете округление таким образом, чтобы оно охватывало оба конца диапазона, не будут ли эти экстремальные значения вдвое менее вероятными, чем любые неэкстремальные значения?

С усечением вы никогда не включите максимум.

Вы уверены, что вам действительно нужен максимум? Буквально почти 0 шансов, что вы попадете точно на максимум.

Тем не менее, вы можете использовать тот факт, что вы отказываетесь от точности, и делать что-то вроде этого:

float MersenneFloat( float min, float max )
{
    double random = 100000.0; // just a dummy value
    while ((float)random > 65535.0)
    {
        //genrand returns a double in [1,2)
        double random = genrand_close1_open2() - 1.0; // now it's [0,1)
        random *= 65536.0; // now it's [0,65536). We try again if it's > 65535.0
    }
    //return in desired range
    return min + float(random/65535.0) * (max - min);
}

Обратите внимание, что теперь у вас есть небольшая вероятность нескольких вызовов genrand при каждом вызове MersenneFloat. Таким образом, вы отказались от возможной производительности за короткий промежуток времени. Поскольку вы понижаете удвоение до двойного, вы не жертвуете точностью.

Изменить: улучшенный алгоритм

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top