在C语言中将一个double截断为一个float

在C语言中将一个double截断为一个float,c,floating-point,double,precision,floating-accuracy,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]。我确信这很简单,但我希望能得到一

这是一个非常简单的问题,但也是一个重要的问题,因为它对我的整个项目影响巨大

假设我有以下代码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]。我确信这很简单,但我希望能得到一些帮助。

在默认IEEE 754舍入模式下,当转换为
float
时,双精度
舍入到1或更多的值是
0x1。ffffff p-1
(在C99的十六进制表示法中,因为您的问题被标记为“C”)

你的选择是:

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

    您可以做的不多-您的
    int
    保留32位,但
    float
    的尾数仅保留24位。取整即将发生。您可以将处理器取整模式更改为向下取整,而不是最近取整,但这将导致一些您希望避免的副作用,特别是如果您不恢复取整当你完成的时候

    您使用的公式没有问题,它为给定的输入生成了尽可能准确的答案。只有一个最终情况不符合硬要求。测试特定的最终情况并用符合要求的最接近的值替换它没有问题:

    if (f >= 1.0f)
        f = 0.99999994f;
    
    0.999999403953552224609375是IEEE-754浮点在不等于1.0的情况下可以采用的最接近值。

    在C中(自C99以来),您可以使用from
    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);
    }
    
    #包括
    #包括
    int main()
    {
    #布拉格STDC FENV_通道
    fesetround(feu向下);
    //volatile——取消GNU gcc和其他不支持FENV的人的注释
    无符号长x=0xffffffff;
    浮点f=(浮点)(双精度)x*(双精度)2.328306436538696e-010;//x/2^32
    printf(“%.50f\n”,f);
    }
    

    使用IBM XL、Sun Studio、clang、GNU gcc进行测试。这给了我
    0.9999994039535522246093750000000000000000000000000000000000000000000
    在所有情况下

    您只需将值截断到最大精度(保持24个高位),然后除以2^24即可得到浮点可以表示的最接近的值,而无需四舍五入到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
    
    无符号整数i=0xffffffff;
    浮动值=(浮动)(i>>8)/(1>0.999999403953552461
    >>>0x1.fffffep-1
    
    我最终的解决方案是缩小常量乘数的大小。这可能是最好的解决方案,因为用双精度乘法没有任何意义。转换为浮点后,精度就不明显了


    因此,
    2.328306436538696e-010
    被更改为
    2.3283063

    这是一个C++11函数吗?@MarkB C99函数,包含在C++11中,在进行转换后,是否有可能使这段代码立即返回舍入方向?@PatriciaShanahan当然,我只展示了一个最小的示例这可能是一个好方法,如果将每个值舍入为零(而不仅仅是接近1的值)适合该操作。没有必要进行演示。我们可以使用
    %a
    格式说明符来显示浮点数,以说明浮点数的组成。@EricPostChil感谢
    %a
    格式,我不知道。这不是一个有用的答案。正如其他答案所示(他们已经演示了如何),有些事情你可以做。@EricPostPhischil,这怎么没有帮助呢?它提供了一个有效的解决方案,没有留下一个有效的舍入模式,它将改变所有中间和后续的计算。“你能做的不多”具有误导性且不必要地令人沮丧。关于
    int
    float
    中位的陈述是不相关的;OP不期望精确映射。他们不是要求避免舍入,只是为了控制它。@EricPostpischil,我断言会有舍入,因为问题定义使其不可避免,我给出了my推理。你可以改变舍入的性质,但你无法避免。我的答案是处理边缘情况的唯一答案,同时在所有其他情况下保持尽可能高的精度。