浮点运算潜在的结果不一致问题

开发 开发工具
昨天发现了项目中的一个 bug ,是因为浮点运算的前后不一致导致的。明明是完全相同的 C 代码,参数也严格一致,但是计算出了不相同的结果。我对这个现象非常感兴趣,仔细研究了一下成因。

[[201284]]

昨天阿楠发现了项目中的一个 bug ,是因为浮点运算的前后不一致导致的。明明是完全相同的 C 代码,参数也严格一致,但是计算出了不相同的结果。我对这个现象非常感兴趣,仔细研究了一下成因。

原始代码比较繁杂。在弄清楚原理后,我简化了出问题的代码,重现了这个问题:

static void 
foo(float x) { 
    float xxx = x * 0.01f; 
    printf("%d\n", (int)(x * 0.01f)); 
    printf("%d\n", (int)xx); 

 
int 
main() { 
    foo(2000.0f); 
    return 0; 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

使用 gcc 4.9.2 ,强制使用 x87 浮点运算编译运行,你会发现令人诧异的结果。

gcc a.c -mfpmath=387 
 
19 
20 
  • 1.
  • 2.
  • 3.
  • 4.
前一次的输出是 19 ,后一次是 20 。

这是为什么呢?让我们来看看 gcc 生成的代码,我截取了相关的段落:

flds    16(%rbp) 
 flds    .LC0(%rip) 
 fmulp   %st, %st(1) 
 fstps   -4(%rbp)          ; 1. x * 0.01f 结果保存到内存中的 float 变量中 
 flds    16(%rbp) 
 flds    .LC0(%rip) 
 fmulp   %st, %st(1) 
 fisttpl -20(%rbp)        ; 2. x * 0.01f 结果直接转换为整型 
 movl    -20(%rbp), %eax 
 movl    %eax, %edx 
 leaq    .LC1(%rip), %rcx 
 call    printf 
 flds    -4(%rbp)                 ; 3. 读出 1. 保存的乘法结果 
 fisttpl -20(%rbp) 
 movl    -20(%rbp), %eax 
 movl    %eax, %edx 
 leaq    .LC1(%rip), %rcx 
 call    printf 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

这里我做了三行注释。

首先,0.01 是无法精确表示成 2 进制的,所以 * 0.01 这个操作一定会存在误差。

两次运算都是 x * 0.01f ,虽然按 C 语言的转换规则,表达式中都是 float 时,按 float 精度运算。但这里 gcc 生成的代码并没有严格设置 FPU 的精度控制,在注释 2 这个地方,乘法结果是直接从浮点寄存器转换为整数的。而在注释 1 这个地方,把乘法结果通过 fstps 以低精度形式保存到内存,再在注释 3 的地方 flds 读回。

所以在注释 2 和注释 3 的地方,浮点寄存器 st 内的值其实是有差别的,这导致了 fisttpl 转换为整数后结果不同。

原文链接:https://blog.codingnow.com/2017/07/float_inconsistence.html#more

【本文为51CTO专栏作者“云风”的原创稿件,转载请通过51CTO联系原作者获取授权】

戳这里,看该作者更多好文

责任编辑:xinxiaoliang 来源: 51CTO专栏
相关推荐

2024-05-11 07:37:43

数据Redis策略

2021-04-18 15:01:56

缓存系统数据

2017-06-20 09:42:52

网络安全法数据隐私法网络安全

2024-04-07 09:00:00

MySQL

2013-12-13 14:46:55

OSPFMTU邻接关系

2024-11-18 08:00:00

数据仓库通用语义层商业智能

2013-03-29 11:16:17

2010-06-02 10:53:28

MySQL版本

2022-03-18 10:53:49

数据系统架构

2018-07-15 08:18:44

缓存数据库数据

2021-02-03 08:01:35

SQLServerLIKE

2021-05-27 18:06:30

MySQL编码数据

2018-07-08 07:38:28

数据库缓存数据

2020-07-20 14:06:38

数据库主从同步服务

2021-09-02 07:56:46

HDFSHIVE元数据

2022-03-16 15:54:52

MySQL数据format

2021-01-19 10:39:03

Redis缓存数据

2012-01-11 16:22:35

HTML 5

2023-12-22 10:19:19

数据库锁机制

2011-02-22 14:02:48

vsftpd
点赞
收藏

51CTO技术栈公众号