这是一个非常简单的问题,但却是一个重要的问题,因为它会极大地影响我的整个项目。
假设我有以下代码snipet:
unsigned int x = 0xffffffff;
float f = (float)((double)x * (double)2.328306436538696e-010); // x/2^32
我希望f
类似于0.99999,但相反,它会向上舍入为1,因为它是最近的float
近似值。这不好,因为我需要{0,1)的间隔float
值,而不是[0,1]。我确信这很简单,但我会感激一些帮助。
答案 0 :(得分:8)
在C(自C99开始)中,您可以使用libm
#include <stdio.h>
#include <fenv.h>
int main()
{
#pragma STDC FENV_ACCESS ON
fesetround(FE_DOWNWARD);
// volatile -- uncomment for GNU gcc and whoever else doesn't support FENV
unsigned long x = 0xffffffff;
float f = (float)((double)x * (double)2.328306436538696e-010); // x/2^32
printf("%.50f\n", f);
}
使用IBM XL,Sun Studio,clang,GNU gcc进行测试。在所有情况下,这都会给我0.99999994039535522460937500000000000000000000000000
答案 1 :(得分:3)
在默认IEEE 754舍入模式下转换为double
时,float
舍入为1或更大的值为0x1.ffffffp-1
(以C99的十六进制表示法,因为您的问题已被标记) “C”)。
您的选择是:
(0x1.ffffffp-1 / 0xffffffffp0)
(给予或取一个ULP)以利用完整的单精度范围[0,1]而不获取值1.0f
。方法2 leads to use the constant 0x1.ffffff01fffffp-33
:
double factor = nextafter(0x1.ffffffp-1 / 0xffffffffp0, 0.0);
unsigned int x = 0xffffffff;
float f = (float)((double)x * factor);
printf("factor:%a\nunrounded:%a\nresult:%a\n", factor, (double)x * factor, f);
打印:
factor:0x1.ffffff01fffffp-33
unrounded:0x1.fffffefffffffp-1
result:0x1.fffffep-1
答案 2 :(得分:1)
你无能为力 - 你的int
拥有32位,但float
的尾数只有24位。舍入将会发生。您可以将处理器舍入模式更改为舍入而不是最接近,但这会导致您想要避免的一些副作用,尤其是在完成后不恢复舍入模式时。
您正在使用的公式没有任何问题,它可以为给定的输入生成最准确的答案。只有一个结束案例没有硬性要求。测试特定的最终案例并将其替换为符合要求的最接近的值没有任何问题:
if (f >= 1.0f)
f = 0.99999994f;
0.999999940395355224609375是IEEE-754浮点数可以采用的最接近的值,而不等于1.0。
答案 3 :(得分:1)
您可以将值截断为最大精度(保持24位高位)并除以2 ^ 24以获得float可以表示的最接近的值而不会舍入为1;
unsigned int i = 0xffffffff;
float value = (float)(i>>8)/(1<<24);
printf("%.20f\n", value);
printf("%a\n", value);
>>> 0.99999994039535522461
>>> 0x1.fffffep-1
答案 4 :(得分:0)
我的最终解决方案是缩小我的常数乘数的大小。这可能是最好的解决方案,因为无论如何都没有增加双倍的意义。转换为浮动后没有看到精度。
因此2.328306436538696e-010
已更改为2.3283063