有一段代码让我感到困惑,它在Windows中运行! 这是代码:
#define point_float2uint(x) *((unsigned int *)&x)
float divide_1000(float y)
{
float v = y / 1000.0f;
return v;
}
float divide_1000(int y)
{
float v = float(y) / 1000.0f;
return v;
}
void float_test(void)
{
int num[5] = {67975500, 67251500, 67540620, 69435500, 70171500};
for (int i = 0; i < 5; ++i)
{
int a = num[i];
float af_f = divide_1000(float(a));
float af_i = divide_1000((a));
printf("src num:%d, af_f:%f, %x, af_i:%f, %x\n", num[i], af_f, point_float2uint(af_f), af_i, point_float2uint(af_i));
}
}
这是输出,由vs2005编译:
src num:67975500, af_f:67975.507813, 4784c3c1, af_i:67975.500000, 4784c3c0
src num:67251500, af_f:67251.507813, 478359c1, af_i:67251.500000, 478359c0
src num:67540620, af_f:67540.625000, 4783ea50, af_i:67540.617188, 4783ea4f
src num:69435500, af_f:69435.507813, 47879dc1, af_i:69435.500000, 47879dc0
src num:70171500, af_f:70171.507813, 47890dc1, af_i:70171.500000, 47890dc0
问题是:为什么我使用“ divide_1000 ”,在Windows中获得不同的结果?这不是我想要的! 我发现并非所有整数都有不同的结果,但有些就像上面的代码一样。
这是输出,由debian中的gcc4.4.5编写:
src num:67975500, af_f:67975.507812, 4784c3c1, af_i:67975.507812, 4784c3c1
src num:67251500, af_f:67251.507812, 478359c1, af_i:67251.507812, 478359c1
src num:67540620, af_f:67540.625000, 4783ea50, af_i:67540.625000, 4783ea50
src num:69435500, af_f:69435.507812, 47879dc1, af_i:69435.507812, 47879dc1
src num:70171500, af_f:70171.507812, 47890dc1, af_i:70171.507812, 47890dc1
我在使用不同的功能“ divide_1000 ”时获得相同的结果。这就是我想要的。
答案 0 :(得分:3)
这里涉及很多代码生成设置会影响结果。当使用“经典”FPU指令进行浮点计算时,您报告的差异在默认浮点模型(即“精确”模型)下的非优化代码中是可观察的。
编译器按字面翻译第一个调用:原始整数值首先转换为float
- 4字节浮点值 - 存储在内存中(作为函数参数)。此转换将值舍入为+6.7975504e+7
,这已经不准确了。之后,float
值从第一个函数内部的内存中读取,并用于进一步的计算。
第二个调用将int
值传递给函数,该函数直接加载到高精度FPU寄存器中并用于进一步计算。即使您在第二个函数中指定了从int
到float
的显式转换,编译器也决定忽略您的请求。该值永远不会从字面上转换为float
,这意味着上述精度损失永远不会发生。
这就是造成你观察到的差异的原因。
如果您将第二个功能重写为
float divide_1000(int y)
{
float fy = y;
float v = fy / 1000.0f;
return v;
}
即。添加一个将float
值保存到内存中命名位置的附加步骤,编译器将在非优化代码中执行该步骤。这将导致结果相同。
同样,当编译器通常尝试非常接近地(但并不总是完全)转换所有语句时,上述内容适用于未经优化编译的代码。在优化的代码中,编译器在两种情况下都消除了对float
和所有“不必要的”中间存储器的“不必要的”中间转换,产生相同的结果。
您可能还想尝试其他浮点模型(即“严格”和“快速”)以了解它如何影响结果。这些浮点模型专门用于处理您观察到的问题。
如果更改编译器的代码生成设置并使其使用SSE指令进行浮点运算,结果也可能会发生变化(在我的实验中,当使用SSE2指令集而不是FPU指令时,差异消失了。)