2009-03-13 5 views
3

Это, вероятно, вопрос для эксперта x86 FPU:с плавающей точкой, когда округление усечения

Я пытаюсь написать функцию, которая генерирует случайное значение с плавающей точкой в ​​диапазоне [мин], макс. Проблема в том, что мой алгоритм генератора (с плавающей точкой Mersenne Twister, если вам интересно) возвращает только значения в диапазоне [1,2) - то есть, я хочу инклюзивную верхнюю границу, но мое «исходное» генерируемое значение от исключительной верхней границы. Ловушка здесь заключается в том, что базовый генератор возвращает 8-байтовый двойной, но мне нужен только 4-байтовый float, и я использую режим округления 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, измените ответ на этот вопрос?

Редактировать: Ответ да, усечение в этом случае от double до float является достаточным, чтобы результат включал максимум. См. Ответ Crashworks для получения дополнительной информации.

ответ

4

опам SSE тонко изменить поведение этого алгоритма, поскольку они не имеют промежуточное 80-битное представление - математика действительно делается в 32 или 64 бита. Хорошей новостью является то, что вы можете легко протестировать ее и посмотреть, изменит ли она ваши результаты, просто указав параметр командной строки/ARCH: SSE2 на MSVC, что заставит его использовать скалярные операционные системы SSE вместо инструкций FDA для 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; 

Edit, результат: оригинальный плакат этот тест и обнаружили, что с обрезанием, то 1,99999 будет округлить до 2 с и без/аркой: SSE2 ,

+0

Теперь почему я не подумал запустить этот тест среди других, с которыми я работал :) Я обнаружил, что с усечением, 1.99999 будет округлять до двух с и без/arch: SSE2. Благодаря! –

+0

Рад помочь - мне было любопытно, какой результат теста будет сам. – Crashworks

0

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

+0

Мне кажется, что я просто использую усечение, ответ - да, но если я увеличиваю максимальное значение, ответ будет отрицательным. –

0

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

Вы уверены, что вам действительно нужен максимальный? Существует буквально почти 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. Таким образом, вы отказались от возможной производительности на закрытом интервале. Поскольку вы опускаетесь с двойного на плавающий, вы в конечном итоге не жертвуете никакой точностью.

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

+0

Да, мне нужно, чтобы max был включен (это контракт на библиотечную функцию). Было ли у вас преимущество в том, чтобы делать это по-своему, а не увеличивать значение моего максимального значения до умножения? –

+0

Это может сработать. Однако где-то вам нужно либо выполнить тест на отклонение, либо иметь не-идеальное распределение ценностей. Аналог этой проблемы, скажем, порождает целое число 0-256 из случайного числа int 0-65535. Он просто не отображается равномерно. – rlbond

+0

На самом деле, я просто попробовал предложение Crashworks, и усечение действительно округляет. –