在C语言中将一个double截断为一个float
这是一个非常简单的问题,但也是一个重要的问题,因为它对我的整个项目影响巨大 假设我有以下代码snipet:在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]。我确信这很简单,但我希望能得到一
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
方法20x1.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以来),您可以使用fromlibm
#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推理。你可以改变舍入的性质,但你无法避免。我的答案是处理边缘情况的唯一答案,同时在所有其他情况下保持尽可能高的精度。