探讨float类型的数值,为什么两个float不能直接相等

转载:http://blog.csdn.net/turkeyzhou/article/details/2755970


在程序编写的时候,我们会忽略一些细节上的问题,尤其是写java这种高级语言,久而久之,我们会对底层缺乏认识,这也是为什么前段时间会有人说java对学生有害的原因;近段时间在重新温习操作系统,感觉收获颇丰,甚是欣慰;今天突然发现一个关于float的小问题,若不是仔细回忆,差点就忘记啦,所以来讨论下;public class FloaTest{public static void main(String[] args){float floatNumberA=1.0;float floatNumberB=0;for(int addCount=0;addCount<10;addCount++){floatNumberB+=0.1;}if(floatNumberB==floatNumberA){System.out.println("equals"); }else{System.out.println("unequals"); }}}

输出的结果却出乎我们意料,是unequals;

 

原因在于计算机在内存表示float的时候都是有误差的,我们应该想起在内存float的表示形式;

下面引自一边别的文章;

     首先说一下原,反,补,移码. 移码其实就等于补码,只是符号相反. 对于正数而言,原,反,补码都一样, 对负数而言,反码除符号位外,在原码的基础上按位取反,补码则在反码的基础之上,在其最低位上加1,要求移码时,仍然是先求补码,再改符号.
浮点数分为float和double,分别占4,8个字节,即32,64位. 我仅以32位的float为例,并附带说double.
在IEEE754标准中,规定,float的32位这样分:
    符号位(S)
1 阶码(E)
8 尾数(M)
23

这里应该注意三点:  
         A,阶码是用移码表示的,这里会有一个127的偏移量,它的127相当于0,小于127时为负,大于127时为正,比如:10000001表示指数为129-127=2,表示真值为2^2,而01111110则表示2^(-1).
          B, 尾数全都是小数点后面的数,
          C, 但尾数中省略了一个1,因此尾数全为0时,也是1.0...00;
接下来只要说明几个问题就明白了,以123.456为例,表示为二进制就是:N (2) = 1111011. 01110100101111001 ,这里,会右移6位,得到N (2) = 1.111011 01110100101111001*2^6; 这种形式就可以用于上图中的表示格式了.              
符号位(S)
          0 阶码(E) 00000110 尾数(M) 11101101110100101111001


        注意到,上面的阶码第一位为0表正,尾数比N(2)表示的第一位少了个1,这就是上面说的默认为第一位为1. 由于在将十进制转为二进制的过程中,常常不能正好转得相等, (当然,像4.0这样的就不会有损失,而1.0/3.0这样的必然损失),所以就产生了浮点数的精度问题, 实际上,小数点后的23位二进制数,能影响的十进制数的前8位,这是为什么呢?一般人在这时往往迷迷胡胡了,其实很简单,在上面表示的尾数中,是二进制的,小数点后有23位,最后一位的值为1时,它就是1/2^22=0.000000238实际取的时候肯定是0.0000002,也就是说,对于一个float型的浮点数,其有效的位数是从左到右数7位(包括缺省的1才是7位),当到达上面这个第8位时,就不可靠了,但我们的VC6可以输出最长的1.0/3.0为0.33333333333333331,这主要是编译器的问题了, 而并不是说浮点数小数点后的16位都有效. 如果不信的话,可以去试一下double类型的1.0/3.0, 得到的也将是小数点后17位.                                                                                                  ..另外,编译器或电路板一般都有"去噪声"的"修正"能力,它能够使得超过7位的十进制数即使无效了也不会变得离谱,这也是上面为什么一直都是输出333而不是345之类的,. 可以这样试一下:
float f=123456789;
cout<<f<<endl;//这里肯定得到123456789.
这里有一个被人遗忘的问题,就是10进制小数怎么变为2进制小数,其实很简单,就是将10进的小数部分不断乘以2,进位时就将对应的2进制位写入1. 因此将上面的N (2) = 1.111011 01110100101111001*2^6;再转回十进制数时,很可能已经不再是123.456了. 好,精度问题应该说清楚了. 下面说示数范围.
阶码的示数位数是8位移码,最大为127最小为-127,这里的127用来作为2的指数,因此为2^127,约等于 1.7014*10^38, 而我们知道,float的示数范围约为-3.4*10^38-------3.4*10^38, 这是因为尾数的24位(默认第一位为1)全为1是,非常接近2, 1.11..11很明显约为2,因此浮点数的范围就出来了.
double的情况与float完全相似,只是它的内在形式是
    符号位(S)
          1 阶码(E)
11 尾数(M)
   52


主要的区别在于它的阶码有11位了, 这就有2^1023约等于 0.8572*10^308, 尾数53位约为2,故double的示数范围约为 -1.7*10^308.------1.7*10^308. 至于其精度,同样,1.0/2^51=4.4*10^(-16).小数点后15位有效,加上缺省的那一位,因此对于double浮点数,从左到右的16位数都是可靠的.
有时,我们会听到"定点小数"这个词,单片机(如手机等)一般只使用定点数,迷糊的时候,我们会以为 float a=23.4; 这种是定点小数, float a=2.34E1这种为浮点数,其实这是错误的, 上面只是同一个浮点数的不同表示,都是浮点数. 定点小数是有这种提法,认为整就是定点小数,小数点定在个位后面,小数部分为0.也可认为纯小数是定点小数,但它只能表示小于1的纯小数.
然后再说一下C/C++中的几个函数, C++中默认输出小数点后的5位小数,但可以设置,有两种方法:调用setpression或者使用cout.pression,但效果是不同的:
float mm=123.456789f;
cout<<mm<<endl; //123.457            虽说默认为不数点后5位,但只是整数部分只有一位才这样.
setprecision(10);                               //设置小数点后的位数,但当整数部分有两位时,与默认情况没什么两样,不起作用.
cout<<mm<<endl; //123.457
cout.precision(4);                              //设置总的位数.
cout<<mm<<endl; //123.4      总之效果是比较怪的,个人认为虽然这样显得不够确定,但实为硬件系统所限.无可厚非.
对于0的实际表示,有人认为+0一般能绝对为0,而-0则可能表示一个极小的数. 为此,本人想到了一种很好的验证办法,证明了不管+0还是-0,它都是2^(-127),代码如下:
float fDigital = 0.0f;       
unsigned long nMem;// 临时变量,用于存储浮点数的内存数据
// 将内存按位复制到临时变中,以便取用,此时的nMem并不等于fDigital了,它是按位复制的。
nMem = *(unsigned long*)&fDigital;
cout<<nMem<<endl; //一般得到一个很大的整数.
bitset<32>mybit(nMem);//妙在此处,这里的输出就是32float的内存表示了.终于完全直观地看到了.
cout<<mybit<<endl;   //00000000000000000000000000000000 用-0.0来试,也是如此.
如果你还认为上面那一长串的0表示的是绝对的0,那么请重新看本文. 事实上,本人的这种做法是比较巧妙的,将上面的fDigital用任何其它浮点数表示,这个bitset数都可以反映出它的内存表示.
有移码表示阶码有是有原因的,主要是移码便于对阶操作,从而比较两个浮点数的大小. 这里要注意的是,阶码不能达到11111111的形式,IEEE规定,当编译器遇到阶码为0XFF时,即调用溢出指令. 总之,阶码化为整数时,范围是:-126~127.
最后,有一个往往高手也汗颜的地方,一定要记住,浮点数没有无符号型的usinged float/double是错误的.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/294093.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android ifw 启动广告,使用 IFW 完全控制 Android 应用行为 | 实用技巧

我们常说 Android 系统最大的优点是开放性与包容性&#xff0c;但这也造成了应用行为不容易掌控的缺点。特别是国内应用与系统&#xff0c;失去了谷歌 Play 商店官方的应用审核政策后普通用户根本无法确定应用获取的权限用来干什么&#xff0c;又在后台执行了哪些行为。谷歌最近…

VS2022安装教程和使用说明来了

我看很多小伙伴已经开始迫不及待的安装VS2022了&#xff0c;虽然我也安装了VS2022&#xff0c;但是我依旧使用VS2019。因为我觉得适合我的才是最好的&#xff0c;并非是最新的&#xff0c;所以大家在使用的时候&#xff0c;根据实际需求选择开发工具&#xff0c;不要一味追求最…

华为交换机RRPP配置实验

在工作中遇到了H3C和HW的RRPP配置&#xff0c;以下就以华为模拟器再作一次实验。大家共同来论讨论遇到的问题。 【理论基础】RRPP具体的理论见配置手册下面只点几个容易出错的地方1、作为RRPP环的接口要关闭STP2、两个重要的命令&#xff1a;control-vlan vlan-id命令&#xff…

Android之AIDL服务

AIDL服务 服务&#xff08;Service&#xff09;是android系统中非常重要的组件。Service可以脱离应用程序运行。也就是说&#xff0c;应用程序只起到一个启动Service的作用。一但Service被启动&#xff0c;就算应用程序关闭&#xff0c;Service仍然会在后台运行。 andro…

男人的那些统一话术......

1 当面试官来租你的房子▼2 好家伙&#xff08;via.dy油画艺术&#xff09;▼3 学到了&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼4 一杯奶茶能加多少料▼5 原来我们如此优秀&#xff01;&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼6 幼儿园里卧…

后端开发者开发前端必会的工具(一):样式调试篇

又来为大家分享干货了&#xff0c;今天主要是分享一点关于后端工程师开发前端比较苦恼的一个问题《如何去调试前端&#xff1f;》&#xff0c;我相信这是所有后端开发者比较困惑的&#xff0c;如果有这个困惑的&#xff0c;记得关注“程序员晓晓”公众号&#xff0c;并给我留言…

C#编程中的66个好习惯,你有多少个?(转)

http://www.cnblogs.com/jxsoft/archive/2012/01/11/2318824.html转载于:https://www.cnblogs.com/ein-key5205/p/3592583.html

sqlite数据库的char,varchar,text,nchar,nvarchar,ntext的区别

1、CHAR。CHAR存储定长数据很方便&#xff0c;CHAR字段上的索引效率级高&#xff0c;比如定义char(10)&#xff0c;那么不论你存储的数据是否达到了10个字节&#xff0c;都要占去10个字节的空间,不足的自动用空格填充。 2、VARCHAR。存储变长数据&#xff0c;但存储效率没有CHA…

配置 mybatis的 log4j.properties

log4j.rootLoggerdebug,stdout,logfile### 把日志信息输出到控制台 ### log4j.appender.stdoutorg.apache.log4j.ConsoleAppender #log4j.appender.stdout.TargetSystem.err log4j.appender.stdout.layoutorg.apache.log4j.SimpleLayout### 把日志信息输出到文件&#xff1a;jb…

linux 下 oracle 10.2.0.1 32bit netca报错

现象如下&#xff1a; 今天一同事安装完数据库软件运行netca创建监听时报错(运行netmgr与dbca均可执行成功)&#xff0c; 报错信息: [oracleWEB01A bin]$ netca Oracle Net Services Configuration: # # An unexpected error has been detected by HotSpot Virtual Machine:…

adb android源码分析,Android源码分析(十六)----adb shell 命令进行OTA升级

一: 进入shell命令界面adb shell二&#xff1a;创建目录/cache/recoverymkdir /cache/recovery 如果系统中已有此目录&#xff0c;则会提示已存在。三: 修改文件夹权限chmod -R 777 /cache/recovery四: 把ota文件路径写入/cache/recovery/command文件中echo "--update_pac…

如何使用cURL获得请求和响应时间?

✎ 码甲说 hello&#xff0c;老伙计们&#xff0c;又有半个多月没见了&#xff0c;今天给大家分享一个干货编程小技巧&#xff0c;上至架构师、下至开发者、运维男、QA&#xff0c; 得此利器&#xff0c;事半功倍。cURL在我的眼里&#xff0c;就是一个httpClient手办&#xff…

ASP.NET MVC CheckBoxFor为什么会生成hidden input控件

自己开发的公众号&#xff0c;可以领取淘宝内部优惠券 Html.CheckBoxFor(m > m.Bool) 使用CheckBoxFor方法得到的html代码会是下面这个样子 <input checked"checked" data-val"true" data-val-required"Bool 字段是必需的。" id"Bool…

Android Caused by: java.lang.IllegalArgumentException: column '_id' does not exist

出错原因&#xff1a;在查询整个sqlite数据库时&#xff0c;没有查询到 "_id" 这一列。 原来的代码是&#xff1a;mSQLiteDatabase.query(table_name, new String[] {_title}, null, null, null, null, null); 修改后的代码为&#xff1a;mSQLiteDatabase.query(ta…

linux挂载iso文件

mount -o loop -t iso9660 /root/winxp.iso /mnt 转载于:https://blog.51cto.com/xitong/1148436

android 远程调试工具,Android远程调试的探索与实现

文章来源&#xff1a;美团点评技术团队作为移动开发者&#xff0c;最头疼的莫过于遇到产品上线以后出现了bug&#xff0c;但是本地开发环境又无法复现的情况。常见的调查线上棘手问题方式大概如下&#xff1a;方法优点缺点联系用户安装已添加测试日志的APK方便定位问题需要用户…

.NET 6新特性试用 | 自动生成高性能日志记录代码

前言要想记录日志&#xff0c;常用的方式是访问ILogger实例提供的日志记录方法&#xff1a;private readonly ILogger<WeatherForecastController> _logger;public WeatherForecastController(ILogger<WeatherForecastController> logger) {_logger logger; }[Htt…