截断时浮点舍入

时间:2009-03-13 21:25:31

标签: c floating-point x86 fpu

这可能是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的答案。

3 个答案:

答案 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。因此,您已经放弃了关闭间隔的可能性能。既然你是从双向下转换到浮动,你最终会牺牲精度。

编辑:改进算法