问题背景

背景就简单点儿说,当初一个项目 C# 编写,涉及浮点运算,来龙去脉省去,直接看如下代码。(为什么有这个问题产生,是因为当初线上产生了很诡异的问题,和本地调试效果不一致。)

float p3x = 80838.0f;float p2y = -2499.0f;double v321 = p3x * p2y;Console.WriteLine(v321);

很简单吧,马上笔算下结果为 -202014162,没问题,难道C#没有产生这样的结果?不可能吧,开启 VisualStudio,copy代码试试,果然结果是-202014162。就这样完了么?显然没有!把编译时的选项从AnyCPU改成x64试试~(服务器环境正是64位滴哦!!)结果居然变成了-202014160,对没错,就是-202014160。细想一下,因为浮点运算的误差,-202014160 这个结果是合理的。嗯,再试试C++。// 测试环境Intel(R) i7-3770 CPU, windows OS 64. Visual Studio 2012 默认设置。

float p3x = 80838.0f;float p2y = -2499.0f;double v321 = p3x * p2y;std::cout.precision(15);std::cout << v321 << std::endl;

呃,好像x86、x64都是这个合理的结果 -202014160。奇了个怪了。其实上面这段C++代码在不同的平台下的结果如下:

  • Windows 32/64位下:-202014160

  • Linux 64位下(CentOS 6 gcc 4.4.7):-202014160

  • Linux 32位下(Ubuntu 12.04+ gcc 4.6.3)是:-202014162

补充说明:当初这篇文章投稿到酷壳,著名程序员左耳朵耗子那边,这部分结果数据来自耗子叔对文章做的部分调整。(因为当初行文没抓住重点,还引来了不少吐槽)

合理的运算结果,应该是-202014160,正确的运算结果是-202014162,合理性是浮点精度不够造成的(后文解释了合理性)。若是用两个double相乘可得正确且合理的运算结果。// 就别纠结我用的“正确、合理”这两个词是否恰当了。问题是为何C#下X64和X86结果不一致?

浮点运算结果错误但合理的解释

为何  80838.0f * -2499.0f = -202014160.0 是合理的?

32位浮点数在计算机中的表示方式为:1位符号位(s)-8位指数位(E)-23位有效数字(M),即:

其中E是实际转换成1.xxxxx*2^E的指数,M是去掉 1 后的前面的xxxxx(节约1位)。

1.  80838.0 如何表达?

80838.0 = 1 0011 1011 1100 0110.0(二进制) = 1.0011 1011 1100 0110 0*2^16

有效位M = 0011 1011 1100 0110 0000 000(一共 23 位)

指数位E = 16 + 127 = 143 = 10001111

内部表示 80838.0 = 0 [10001111] [0011 1011 1100 0110 0000 000] = 0100 0111 1001 1101 1110 0011 0000 0000 = 47 9d e3 00 //实际调试时看到的内存值 可能是00 e3 9d 47是因为调试环境用了小端表示法法:低位字节排内存低地址端,高位排内存高地址

2. -2499.0 如何表达?

-2499.0 = -100111000011.0 = -1.001110000110 * 2^11

有效位M = 0011 1000 0110 0000 0000 000

指数位E = 11+127=138= 10001010

符号位s = 1

内部表示-2499.0 = 1 [10001010] [0011 1000 0110 0000 0000 000]

=1100 0101 0001 1100 0011 0000 0000 0000 =c5 1c 30 00

3. 如何计算 80838.0 * -2499.0 = ?

指数 e = 11+16 = 27

则指数位 E = e + 127 = 154 = 10011010

有效位相乘结果为 1.1000 0001 0100 1111 1011 1010 01 (可以自己动手实际算下),实际中只能有23位,后面的被截断即1000 0001 0100 1111 1011 1010 01,相乘结果内部表示=1[10011010][1000 0001 0100 1111 1011 101] = 1100 1101 0100 0000 1010 0111 1101 1101 = cd 40 a7 dd

结果 = -1.1000 0001 0100 1111 1011 101 *2^27

= -11000 0001 0100 1111 1011 1010000

= -202014160

通过上面得知,32 位浮点数,-202014160 就是合理的结果,完全能解释清楚。但如果有效数字更长的话, 上面的就不会被截断。

4. 正确的结果-202014162怎么得来?

有效位相乘结果为 1.1000 0001 0100 1111 1011 1010 01

即结果 = -1.1000 0001 0100 1111 1011 101001 *2^27

= -11000 0001 0100 1111 1011 101001 = -202014162

根因挖掘

上面部分解释了两种结果的来源,但貌似没从根本回到为什么?用C++同样的代码,X86,X64(DEBUG下,这个后面会说)下得到一致的结果-202014160,容易理解且也是合理的。原因何在?看下编译后生成的代码(截取关键部分)

//C# x86 下......float p3x = 80838.0f;0000003b mov dword ptr [ebp-40h],479DE300hfloat p2y = -2499.0f;00000042  mov dword ptr [ebp-44h],0C51C3000hdouble v321 = p3x * p2y;00000049  fld dword ptr [ebp-40h]0000004c fmul dword ptr [ebp-44h]0000004f  fstp qword ptr [ebp-4Ch].......

//C# X64下......float p3x = 80838.0f;00000045  movss xmm0,dword ptr [00000098h]0000004d  movss dword ptr [rbp+3Ch],xmm0float p2y = -2499.0f;00000052  movss xmm0,dword ptr [000000A0h]0000005a movss dword ptr [rbp+38h],xmm0double v321 = p3x * p2y;0000005f  movss xmm0,dword ptr [rbp+38h]00000064  mulss xmm0,dword ptr [rbp+3Ch]00000069  cvtss2sd xmm0,xmm00000006d  movsd mmword ptr [rbp+30h],xmm0......

C++ x86 / x64下都生成了类似的代码(这也就是为何 C++ x86/x64与C#x64结果一致)即都用了先用浮点乘起来(mulss),然后转成double(cvtss2sd)。从上面的汇编代码可以看出 C# X86生成代码用的指令fld/fmul/fstp等。其中fld/fmul/fstp等指令是由FPU(float point unit)浮点运算处理器做的,FPU在进行浮点运算时,用了80位的寄存器做相关浮点运算,然后再根据是float/double截取成32位或64位。非FPU的情况是用了SSE中128位寄存器(float实际只用了其中的32位,计算时也是以32位计算的),这就是导致上述问题产生的最终原因。

浮点运算标准IEEE-754 推荐标准实现者提供浮点可扩展精度格式(Extended precision),Intel x86处理器有FPU(float point unit)浮点运算处理器支持这种扩展。C#的浮点是支持该标准的,其中其官方文档也提到了浮点运算可能会产生比返回类型更高精度的值(正如上面的返回值精度就超过了float的精度),并说明如果硬件支持可扩展浮点精度的话,那么所有的浮点运算都将用此精度进行以提高效率,举个例子x*y/z, x*y的值可能都在double的能力范围之外了,但真实情况可能除以z后又能把结果拉回到double范围内,这样的话,用了FPU的结果就会得到一个准确的double值,而非FPU的就是无穷大之类的了。

即产生如上的结果原因是,两个浮点数相乘在非FPU的情况下,用了32位计算产生的结果导致结果存在误差,而FPU是用了80位进行计算的,所以得到的结果是精度很高的,体现在本文的案例上就是个位数上的2。所以大家在写代码的时候得保证实际运行环境/测试环境/开发环境的一致性(包括OS架构啊、编译选项等)啊,不然莫名其妙的问题会产生(本文就是开发环境与运行环境不一致导致的问题,纠结了好久才发现是这个原因);遇到涉及浮点运算的时候别忘了有可能是这个原因产生的;另外,float/double混用的情况得特别注意。

总结一下,本文通过分析之前遇到的一个疑难杂症带着大家一块回顾或者学习了一下计算机内部浮点数的表达,解决了疑问。有时候可能需要跟进到硬件底层,当然随着硬件技术的发展,可能以前理所当然的东西在新硬件的情况下也会有所不同(例如文中提到的 FPU 也有更高端的技术来替换了,本人

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼

如有收获,点个在看,诚挚感谢

活动平台为何没有x86?_一个由跨平台产生的浮点数bug | 有你意想不到的结果相关推荐

  1. 一个由跨平台产生的浮点数bug | 有你意想不到的结果

    问题背景 背景就简单点儿说,当初一个项目 C# 编写,涉及浮点运算,来龙去脉省去,直接看如下代码.(为什么有这个问题产生,是因为当初线上产生了很诡异的问题,和本地调试效果不一致.) float p3x ...

  2. 微盟合作,重磅推出全免费的H5专业营销平台,快速创建第一个H5活动(捷微H5)

    重要通知 亲爱的小伙伴们: 好消息!免费的H5营销活动平台(捷微H5)重磅推出,扫描即可登录,无需注册. 一平台官网 登录官网: www.h5huodong.com 二扫描登录 点击登录,选择扫描登录 ...

  3. 学生社团网站html,学生社团活动平台的设计与实现.docx

    PAGE 67 学生社团活动平台的设计与实现 摘 要 本系统立足于实现社团活动申请与审批.资源申请与审批等工作,面向高校中所有的社团,建立一个使用便捷.可靠的社团活动平台,从而更方便地进行社团活动的申 ...

  4. 修改webpack的publicPath为动态设置以适配公司活动平台

    背景: 我们需要将React开发的应用部署到一个活动搭建平台上,这意味我们只需要上传源码,没有搭建服务器的环节,没有配置Nginx的环节.具体步骤就是在该平台新建一个活动,然后将自己的源码传到这个活动 ...

  5. c语言帮助记忆单词的小程序,帮助记忆单词的书课堂活动微信小程序软件_速记背单词...

    帮助记忆单词的书课堂活动微信小程序软件_速记背单词 下载地址: 内容预览 快速记忆单词的方法bewilderment.mp4 快速记忆单词的方法bewitching.mp4 快速记忆单词的方法beyo ...

  6. 2019专业创业赛事活动平台

    2019专业创业赛事活动平台 创成汇--专业的创新创业成果转化服务平台.主要服务的对象是中小微企业   创成汇为载体,整合了行业内优质的资源,不管是在品宣.创赛等方面为你提供"一站式&quo ...

  7. 未来架构师的平台战略范例(4)_大数据

    <未来架构师>的平台战略范例(4) 作者:高焕堂,misoo.tw@qq.com 首页:Back                                        主题:决策爱 ...

  8. 如何构建营销活动平台(一):前言概述

    背景 笔者接触营销活动已经接近半年时间,围绕C端用户的邀新,留存,促活也上线了一些营销活动,为了实现组件化,在编程过程中也一直在思考如何搭建一个营销平台, 从而实现运营活动的配置化和扩展性,忙中偷闲, ...

  9. 如何构建营销活动平台(二):业务功能设计

    营销活动主流程 功能描述 模块介绍 活动管理 新建活动:a.常用活动模板 b.现有活动上修改 c.新活动 活动管理:a.发布活动 b.暂停活动 c.终止活动 d.继续活动 渠道投放 活动形式管理 数据 ...

最新文章

  1. python操作excel表格-Python学习—对excel表格的操作
  2. CentOS7性能监控系统安装
  3. 一根Express Route同时支持ARM和ASM的VNET
  4. RHEL5下源码配置LAMP全记录
  5. (十三)真题模拟【告诉你答案是什么】
  6. linux中call命令,各种linux命令(pwn)
  7. [转]redis 5.0.5 5分钟搭建redis集群
  8. 前端接收pdf文件_原生ajax请求获取pdf文件流本地下载(支持谷歌IE)
  9. PDF文件的书签批量自动导入和导出 PDFBookmark-Exchanger
  10. 【ionic框架bug】slideBox在使用model或pop后width变为0的解决方案
  11. [原创] 原创鼠繪【白发紫衣妖艳男子】
  12. 80psi等于多少kpa_【图】关于胎压的换算psi、bar,kpa
  13. @huangcheng: Fedora 9 GDM开启XDMCP
  14. windows7系统,ping本机ip地址请求超时的解决方案
  15. 手把手教你使用Python生成图灵智能小伙伴,实现工作助手/闲聊功能
  16. 【spark】八 自己实现 RDD中 某列 累计百分比确定阈值
  17. 什么是接口测试?测试人员为什么要做接口测试?
  18. Android11 刷机教程
  19. BQB 蓝牙无线技术
  20. 律所CRM软件,适用于律师事务所的系统

热门文章

  1. java 中的servlet_java中的Servlet
  2. 台达b2伺服说明书_三菱Q系列定位模块及伺服参数不会设置?看这一篇就够了!...
  3. onpagefinished等了很久才执行_其实,无所事事的日子才过得最累
  4. 旋转矩阵求旋转角度_(加餐)欧拉角及矩阵旋转
  5. 找工作,要工资高的,还是要自己喜欢的?
  6. 面试官:“你大学做过真实项目吗?”
  7. h700通话糊 索尼wi_专业的事还是要交给专业的人,盘点索尼千元以下的好耳机...
  8. 生日快乐程序_祝肖战1005生日快乐-用R给他画个蛋糕爱心吧
  9. harbor登录验证_Harbor镜像仓库的安装与配置
  10. 利用python 对比相似度_头条、油条商标有多像?Python检测发现相似度高达98.4%