将float浮动到int,32位C的区别

cpa*_*tor 11 c casting 32-bit

我目前正在使用需要运行32位系统的旧代码.在这项工作中,我偶然发现了一个问题(出于学术兴趣),我想了解其原因.

如果对变量或表达式进行强制转换,似乎在32位C中从float转换为int的行为会有所不同.考虑该计划:

#include <stdio.h>
int main() {
   int i,c1,c2;
   float f1,f10;
   for (i=0; i< 21; i++)  {
      f1 = 3+i*0.1;
      f10 = f1*10.0;
      c1 = (int)f10;
      c2 = (int)(f1*10.0);
      printf("%d, %d, %d, %11.9f, %11.9f\n",c1,c2,c1-c2,f10,f1*10.0);
   }
}
Run Code Online (Sandbox Code Playgroud)

使用-m32修饰符直接在32位系统或64位系统上编译(使用gcc),程序的输出为:

30, 30, 0, 30.000000000 30.000000000
31, 30, 1, 31.000000000 30.999999046
32, 32, 0, 32.000000000 32.000000477
33, 32, 1, 33.000000000 32.999999523
34, 34, 0, 34.000000000 34.000000954
35, 35, 0, 35.000000000 35.000000000
36, 35, 1, 36.000000000 35.999999046
37, 37, 0, 37.000000000 37.000000477
38, 37, 1, 38.000000000 37.999999523
39, 39, 0, 39.000000000 39.000000954
40, 40, 0, 40.000000000 40.000000000
41, 40, 1, 41.000000000 40.999999046
42, 41, 1, 42.000000000 41.999998093
43, 43, 0, 43.000000000 43.000001907
44, 44, 0, 44.000000000 44.000000954
45, 45, 0, 45.000000000 45.000000000
46, 45, 1, 46.000000000 45.999999046
47, 46, 1, 47.000000000 46.999998093
48, 48, 0, 48.000000000 48.000001907
49, 49, 0, 49.000000000 49.000000954
50, 50, 0, 50.000000000 50.000000000 
Run Code Online (Sandbox Code Playgroud)

因此,显然在转换变量和表达式之间存在差异.请注意,如果float更改为double和/或int更改为问题,则问题也存在,short或者long如果程序编译为64位,则问题也不会显示.

为了澄清,我在这里试图理解的问题不是关于浮点算术/舍入,而是32位内存处理的差异.

该问题已经过测试:

  • Linux版本4.15.0-45-通用(buildd @ lgw01-amd64-031)(gcc版本7.3.0(Ubuntu 7.3.0-16ubuntu3)),程序编译使用:gcc -m32 Cast32int.c

  • Linux版本2.4.20-8(bhcompile@porky.devel.redhat.com)(gcc版本3.2.2 20030222(Red Hat Linux 3.2.2-5)),程序编译使用:gcc Cast32int.c

任何指示,以帮助我了解这里发生了什么是赞赏.

Pau*_*vie 7

使用MS Visual C 2008,我能够重现这一点.

检查汇编程序,两者之间的区别是中间存储和中间转换的结果获取:

  f10 = f1*10.0;          // double result f10 converted to float and stored
  c1 = (int)f10;          // float result f10 fetched and converted to double
  c2 = (int)(f1*10.0);    // no store/fetch/convert
Run Code Online (Sandbox Code Playgroud)

生成的汇编程序将值推送到FPU堆栈,该堆栈将转换为64位然后相乘.对于c1该结果然后被转换回浮动,存储,然后被再次检索并放置在FPU栈上(并转换为再次翻番),用于向一个呼叫__ftol2_sse,一个运行时功能的双为int转换.

因为c2中间值不会转换为float和从float转换并立即传递给__ftol2_sse函数.对于此函数,请参阅将double转换为int的答案.

汇编程序:

      f10 = f1*10;
fld         dword ptr [f1] 
fmul        qword ptr [__real@4024000000000000 (496190h)] 
fstp        dword ptr [f10] 

      c2 = (int)(f1*10);
fld         dword ptr [f1] 
fmul        qword ptr [__real@4024000000000000 (496190h)] 
call        __ftol2_sse
mov         dword ptr [c2],eax 

      c1 = (int)f10;
fld         dword ptr [f10] 
call        __ftol2_sse
mov         dword ptr [c1],eax 
Run Code Online (Sandbox Code Playgroud)

  • 商店和提取物并不重要; 它们只是C语义的实现.在`f1*10.0`和`f10`之间观察到差异的关键原因是前者是'double`表达式,而后者是`float`.赋值`f10 = f1*10.0;`在将`double`转换为`float`时更改该值. (3认同)

Eri*_*hil 5

在"32位系统"中,差异是由f1*10.0使用全double精度的事实引起的,而f10只有float精度因为它是它的类型.f1*10.0使用double精度因为10.0double常数.当f1*10.0被分配给f10,该值的变化,因为它是隐式转换为float,其具有较低的精度.

如果你改用float常数10.0f,差异就会消失.

考虑第一种情况,当时i为1.然后:

  • f1 = 3+i*0.1,0.1是一个double常数,所以算术执行double,结果是3.100000000000000088817841970012523233890533447265625.然后,要将其分配给f1它,它将转换为float,生成3.099999904632568359375.
  • f10 = f1*10.0;,10.0是一个double常数,所以再次执行算术double,结果是30.99999904632568359375.对于赋值f10,将其转换为float,结果为31.
  • 稍后,当打印f10f1*10.0打印时,我们会看到上面给出的值,小数点后面有九位数字,"31.000000000"表示f10,以及"30.999999046".

如果打印f1*10.0f,用float恒定的10.0f,而不是double恒定的10.0,其结果将是"31.000000000",而不是"30.999999046".

(以上使用IEEE-754基本32位和64位二进制浮点运算.)

特别要注意这一点:当转换为赋值时,f1*10.0和之间的区别.虽然C允许实现在计算表达式时使用额外的精度,但它要求实现在赋值和强制转换中放弃这种精度.因此,在符合标准的编译器中,赋值必须使用精度.这意味着,即使程序是为"64位系统"编译的,也应该出现差异.如果不这样做,则编译器不符合C标准.f10f1*10.0floatf10f10 float

此外,如果float更改为double,float则不会发生转换,并且不会更改该值.在这种情况下,不应该f1*10.0和之间存在差异f10.

鉴于该问题报告的差异并未表现为"64位"汇编并且确实表现出来double,因此是否准确报告了这些观察结果是值得怀疑的.为澄清这一点,应显示确切的代码,并应由第三方复制观察结果.