在C中将double翻转为浮点数

时间:2013-08-06 16:25:22

标签: c floating-point double precision floating-accuracy

这是一个非常简单的问题,但却是一个重要的问题,因为它会极大地影响我的整个项目。

假设我有以下代码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]。我确信这很简单,但我会感激一些帮助。

5 个答案:

答案 0 :(得分:8)

在C(自C99开始)中,您可以使用libm

中的fesetround更改舍入方向
#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”)。

您的选择是:

  1. 在转换前将FPU舍入模式转为向下 - 或
  2. 乘以(0x1.ffffffp-1 / 0xffffffffp0)(给予或取一个ULP)以利用完整的单精度范围[0,1]而不获取值1.0f
  3. 方法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