当前位置: 首页 > 科技观察

浮点数计算的潜在不一致

时间:2023-03-18 00:18:55 科技观察

昨天阿南在项目中发现了一个bug,是浮点数计算不一致导致的。明明是一模一样的C代码,参数也严格一致,只是计算出来的结果不一样。我对这种现象很感兴趣,仔细研究了原因。原代码比较复杂。搞清楚原理后,把有问题的代码简化,问题重现:staticvoidfoo(floatx){floatxxx=x*0.01f;printf("%d\n",(int)(x*0.01f));printf("%d\n",(int)xx);}intmain(){foo(2000.0f);return0;}使用gcc4.9.2,用x87浮点运算强制运行,你会发现这个命令有惊人的结果.gcca.c-mfpmath=3871920前面的输出是19,后面的是20。为什么是这样?我们来看看gcc生成的代码。我截取了相关段落:flds16(%rbp)flds.LC0(%rip)fmulp%st,%st(1)fstps-4(%rbp);1.x*0.01f将结果保存到内存中的float变量flds16(%rbp)flds.LC0(%rip)fmulp%st,%st(1)fisttpl-20(%rbp);2.x*0.01f结果直接转为整数movl-20(%rbp),%eaxmovl%eax,%edxleaq.LC1(%rip),%rcxcallprintfflds-4(%rbp);3.读出1.保存的乘法结果fisttpl-20(%rbp)movl-20(%rbp),%eaxmovl%eax,%edxleaq.LC1(%rip),%rcxcallprintf这里我做了三行注释。首先,0.01不能用二进制准确表示,所以*0.01的运算肯定有错误。两次计算都是x*0.01f,虽然按照C语言的转换规则,当表达式都是float时,以float精度进行计算。但是这里gcc生成的代码并没有严格设置FPU的精度控制。注2中,乘法结果直接由浮点寄存器转为整数。Note1中,乘法结果通过fstps以低精度形式保存到内存中,然后在Note3中用flds读回。所以在注2和注3中,浮点寄存器st中的值其实是不同的,导致fisttpl转为整数后的结果不同。原文链接:https://blog.codingnow.com/2017/07/float_inconsistence.html#more【本文为专栏作家“云峰”原创稿件,转载请联系原作者获得授权】点此查看看作者更多好文章