这可能是x86 FPU专家的问题:
我正在尝试编写一个函数,该函数生成[min,max]范围内的随机浮点值。问题是我的生成器算法(浮点Mersenne Twister,如果你很好奇)只返回范围[1,2]中的值 - 即,我想要一个包含上限,但我的“源”生成的值是从独家上限。这里的问题是底层生成器返回一个8字节的双精度数,但我只想要一个4字节的浮点数,而我使用的是最近的默认FPU舍入模式。
我想知道的是,在这种情况下截断本身是否会导致我的返回值包含FPU内部80位值足够接近时的最大值,或者我是否应该增加之前我的最大值的有效位数将它乘以[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的截断足以导致结果包含max。请参阅Crashworks的答案。
答案 0 :(得分:4)
SSE操作将巧妙地改变该算法的行为,因为它们没有中间的80位表示 - 数学真正以32位或64位完成。好消息是你可以通过简单地为MSVC指定/ ARCH:SSE2命令行选项来轻松测试它并查看它是否会改变你的结果,这将导致它使用SSE标量操作而不是x87 FPU指令用于普通浮点数学。
我不确定在整数边界周围的确切舍入行为是什么,但你可以测试看看当1.999 ..通过例如
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将使用和不使用/ arch:SSE2向上舍入为2。
答案 1 :(得分:0)
如果您确实调整了舍入以确保包含范围的两端,那么这些极端值是不是只有非极端值的一半?
答案 2 :(得分: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);
}
请注意,现在,每次调用MersenneFloat时,它都会轻微多次调用genrand。因此,您已经放弃了关闭间隔的可能性能。既然你是从双向下转换到浮动,你最终会牺牲精度。
编辑:改进算法