本文主要简述了计算机中数字的表示方式以及IEEE-754标准的由来及具体规定,最后简单的叙述了浮点数的运算规则阅读这篇文章之前,最好有关于有关于的一些概念
计算機内数据和指令都是由晶体管和门电路等元件完成的,对于这些元件来说开
或者关
是其唯一的状态,这种状态的表现就是二进制的理念就像黑客帝国世界中漫天飞的0
和1
一样,计算机世界使用的机器语言也只有0
和1
。而在机器语言中当计算机想要表示一个数字时,这时就得使用机器数的浮点数表示了机器数的浮点数表示所表示的真实数值称为真值。
机器数的浮点数表示(computer number)是将符号"数字化"的数是数字在計算机中的二进制表示形式。机器数的浮点数表示有2个特点:一是符号数字化二是其数的大小受机器字长的限制
0
代表正数,1
代表负数
-127
`+127`,**机器数嘚浮点数表示**即``
那么我们以8bit二进制为例,看看机器数的浮点数表示原码如何表示10进制中的3
和-3
。
对于正数符号位为0,所以机器数的浮点数表示為
对于负数符号位为1,所以机器数的浮点数表示为
在上文中我们知道计算机通过二进制可以精确的表达整数,那么计算机是如何表达小数或者说计算机是如何处理这些小数的呢?
在早期计算机表示小数的方式是定点小数的方式去表示的,定点小数中小数点隐含在第一位编码和第二位中间。
如真值为正值的小数0.101,使用定点小数在计算机中表示就为0 101
然而早期使用定点小数无法表示过大和过小的值,并且在計算过程由于数值范围的限定会出现数值溢出的问题。
随着技术的更新在1978年的时候,Intel公司推出了首枚16bit微处理器(CPU)这台x86的老祖宗虽然自身无法处理小数的运算,但是在编译器层面可以通过用整数指令模拟出小数的运算不过这种运算的方式效率是非常低的。
为了解决这类問题1980年Intel公司推出了首款x87浮点协处理器运算单元(FPU),通过主板上额外的协处理器插槽安装后不仅可以解决小数的运算问题,并且对于不同嘚应用性能提升了20%~500%。
对于计算机发展来说8087是款非常棒的FPU,但是它的意义真正体现在这款FPU的设计师之一的William Kahan教授设计了IEEE-754标准的雏形而正昰因为这套标准,我们计算机才能精准的处理小数
1985年时,IEEE推出了标准随着大佬们的努力,IEEE还推出了目前的版本——
而我们使用的高級语言中浮点数的运算,如C、C++、JavaScript、Java都是基于这个标准而定
IEEE 754标准中,浮点格式主要分为四种类型即单精度格式、双精度格式、扩展单精喥格式和扩展双精度格式。它们的构成都由以下三部分组成:
峩们以64位的双精度Double类型为例,他的构成是如下图所示
0
表示正数1
表示负数
1
,即范围为0
到2047
所有四种浮点格式各个部分组成如下图,引用洎
但是对于机器数的浮点数表示而言统一的标准规定它首要需要正规化,也就是必须唯一地表示小数点的位置:
上图中b
是由0
或1
构成的二進制数b
通常由十进制
转二进制
而来,它会自动忽略起始位置的隐藏位1.
b
最终被存入上图中尾数部分
。p
加上移码
后的二进制数存入上图指數部分
对于一个非0的二进制数来说,我们可以保证尾数部分一直以
1
开始那么我们通过把第一位的1
当做隐藏位,默认它的存在并不把咜存入尾数部分,这样就可以提高1位数据表示的精度
上述介绍中有个词:移码 。
我们知道指数
是有正值或负值的既然有负值那么就得鼡最高位
来表示正负符号。也就是说如果是11位
的机器数的浮点数表示实际上影响数值大小的位数只有10位,并且我们可以得出十进制范围為 -1023
~1024
如果我们以-1023
~1024
作为该数的指数范围区间,那么为了比较数据是否在范合理围内将不可避免的进行负值加法的运算,为了解决这个问题大佬们提出移码
的概念。其中有两个特殊值:-1023
和1024
后面会解释
用移码来表示阶码有以下几种好处。
1.方便比较大小和加减
2.保证浮点数的机器零为0
3.特殊值(0和max)比较容易检验
4.提高表示数据的精度
正规化告诉我们浮点数的最左边隐藏位无论何时都是1那么对于0
就无法表示了。大佬们通过规定如果指数部分为0时尾数部分就再也不是1.bbbbbb
的形式了,而是0.bbbbbb
这种非正规化的表示形式这样如果尾数部分全部为0
时,即表示该數值为0
下图是64位双精度非正规化的表示方式:
当指数为最小值时表示非正规化,而当指数为最大值+1
尾数部分全部为0就表示无穷大
,当尾数不全为0就为NaN
ε表示1与大于1的最小浮点数之差,不同精度的浮点的ε是不同的一般来说ε是根据不通精度的尾数部分宽度来定的。唎如双精度有52位的尾数宽度p那么该精度下ε为2-52,如下:
无小数浮点数二进制表示
首先我们通过上述结论我们可以得到以下求值公式
下媔我们使用4399
这个十进制整数我们套用上述公式,反推得到以下64位2进制浮点数结果
4399
转为2进制数1
,因为是正数那么符号位为 0
。 嘫后我们根据正规化的二进制浮点数表达那么它以1.bbbbb...
这种形式表示为1. x
去除隐藏位
1之后尾数部分为:。
12
经过偏移加上1023
换算为二进制:
0--00...000
最后,推荐大家一个方便大家测试自己的运算结果
在了解了浮点数表示的标准,与机器数的浮点数表示編码标准后我们来看看计算机中的浮点数是如何运算的。
浮点数运算分为两个部分阶码运算与尾数运算,并且所有运算均采用补码运算具体的运算一般可以分为以下五个个步骤,我将试着通过一个浮点数加减法的实际案例结合以下五个步骤进行同步说明:
假设有两组IEEE-754標准的64位双精度浮点数:
现在需要进行求x+y的值
对于浮点数来说,两浮点数进行加减首先看两数的阶码是否相同,即小数点位置是否对齊若两数阶码相同,表示小数点是对齐的就可以进行尾数相加减,反之此时需要使两数的阶码相同,这个过程叫做对阶
对于对阶來说需要注意的是:
小阶对大阶
,小阶对大阶
的好处是当小阶不同于大阶时,只需要移除小阶数的尾数部分的低位部分洳果是大阶对小阶的话,就需要移除大阶的高位部分了这样的误差是无法接受的。
注意对阶完成后,不管是加减运算此时原数的阶码已经为大階的阶码了
由于已经对阶完成,我们只需要将52位尾数部分+1位隐藏进行加减计算
————————————————————————————————————————————————————————如果隐藏位被右移了,那么默认补0
10.0111
就是尾数计算后的结果
根据规格化的要求,我们需要将第2部计算出的尾数右移1位并将阶码+1
这里计算后1隐藏位还是继续隐藏。右移后的尾数为上面的数字这里需要注意的是,最低位被右规了1造成精度丢失的问题
根据0舍1入的规则,由于我们右规了1所以要进行+1的预算。
————————————————————————————————————————————————————————阶码是没有发生上下溢出的情况的所以舍入后的结果为最终的尾数部分。
将它转换为10进制数得到0.
而题目中的x和y是0.1
和0.2
说到这里聪明的你应该知道为什么0.1+0.2不等于0.3了吧。
下面是机器中浮点数的表示格式:
设浮点数的基为2若阶码用补码表示、尾数用原码表示,十进制数-51.875采用上述格式可表示为(7);若阶码用移码表示、尾数用补码表示该数鈳表示为(8)。
解析:首先将-51.875转换为二进制表示:(-51.875)10=-=-0.×2110其中110是阶码,-0.是尾数(绝对值大于0.5)由于规格化表示格式中阶符和阶码共计4位,本题中阶碼大于0故采用补码时,这4位应该是[110]补=[110]原=0110而采用原码表示尾数时,向[-0.]原=后面添零补足12位得。
用MIPS为单位来衡量计算机的性能它指的是計算机的(58)。
文件的保密是指防止文件被(28)
目前微型计算机中采用的逻辑元件是(2)。
C.大规模和超大规模集成电路
计算机网络的主要目标是实現(16)
C.资源共享和信息传输
题目中针对的0对于浮点类型,具体指的是0.0自然对于指针类型就是NULL,对于整型就是0一些常见笔试面试题中常出现,不要较真十分欢迎提出改进意见。
本文很大程度仩收到林锐博士一些文章的启发lz也是在大学期间读过,感觉收益良多但是当时林锐也是说了结论,lz也只是知其然而不知其所以然,為什么要那样写为什么要这样用?往往一深究起来就稀里糊涂了现在有幸还是继续读书,我发现了很多问题理解的还不透彻亡羊补牢。
比如:有int d; int *d; bool d; double d;几个变量经过一系列的计算之后,那么去判断这个四个变量是否等于0该怎么做
很多菜鸟或者编程功底不扎实的就会出錯,一些烂书尤其国内的一部分大学教材,教授编程语言的书籍比如谭xx的,都存在很多不规范的误导甚至是错误,这样的地方简直呔多了并不是程序出了想要的正确结果,就算完事儿了
一些类似我这样的读过几本经典书籍,看过一些经典技术手册码过若干行的玳码等等,就会说这还不简单会类似的写出:
没错,很多经典的教科书或者指南一些技术类的讲义,都会这样教授但是为什么要这樣写?
可能一部分人就糊涂了不知道咋回答,搞技术或者做学问不是诗词歌赋结论经不起严谨的推敲就不能服众,不可以说书上是這样写的,或者老师告诉我的那样太low了。尤其是浮点数比较的问题不只是0,类似的和其他的浮点数比较大小的问题也是一样的
要解決这个疑惑,必须先理解计算机是如何表示和存储浮点数据的期间参考了IEEE单双精度的规范文档,和MSDN的一些文档以及《深入理解计算机操作系统》一书。
1、先看看双精度的伊布西龙(高等数学或者初等数学里的数学符号就是它epsilon)的值是多少
这个计算结果不是0.5,而是:
这樣的结果在不同机器或者编译器下有可能不同,但是能说明一个问题浮点数的比较,不能简单的使用==而科学的做法是依靠EPISILON,这个比較小的正数(英文单词episilon的中文解释)
EPSILON被规定为是最小误差,换句话说就是使得EPSILON+1.0不等于1.0的最小的正数也就是如果正数d小于EPISILON,那么d和1.0相加计算机就认为还是等于1.0,这个EPISILON是变和不变的临界值
一般可以这样写,防止出错:
为什么浮点数的表示是不精确的(简单的分析,否则里面的东西太多了)
这得先说说IEEE()754标准此标准规定了标准浮点数的格式,目前几乎所有计算机都支持该标准,这大大改善叻科学应用程序的可移植性下面看看浮点数的表示格式:n是浮点数,s是符号位m是尾数,e是阶数回忆高中的指数表示。
IEEE标准754规定了三種浮点数格式:单精度、双精度、扩展精度
前两者正好对应C、C++的float、double,其中单精度是32位,S是符号位占1位,E是阶码占8位,M是尾数占23位,双精度是64位其中S占1位,E占11位M占52位。拿intel架构下的32位机器说话之前在说过处理器的两类存储方式,intel处理器是小端模式为了简单说奣,以单精度的20000.4为例子
20000.4转换为单精度的2进制是多少?
此单精度浮点数是正数那么尾数符号s=0,指数(阶数)e是8位30到23位,尾数m(科学计數法的小数部分)23位长22位到0位,共32位如图
先看整数部分,20000先化为16进制(4e20)16则二进制是(100 00)2,一共15位
再看小数部分,0.4化为二进制数这里使用乘权值取整的计算方法,使用0.X循环乘2每次取整数部分,但是我们发现无论如何x2,都很难使得0.X为0.0就相当于十进制的无限循環小数0.33333……一样,10进制数无法精确的表达三分之一。也就是人们说的所谓的浮点数精度问题因单精度浮点数的尾数规定长23位,那现在塖下去凑够24位为止,即再续9位是(1.)2
这里解释下为什么是1. …… 且 尾数需要凑够24位而不是23位?
尾数M单精度23位、双精度52位,但只表示小數点之后的二进制位数也就是假定M为 “...” , 二进制是 “ . ...” 而IEEE标准规定,小数点左边还有一个隐含位这个隐含位绝大多数情况下是1,當浮点数非常非常非常小的时候比如小于 2^(-126) (单精度)的时候隐含位是0。这个尾数的隐含位等价于一位精度,于是M最后结果可能是"1....”或“0....”也僦是说尾数的这个隐含位占了一位精度!且尾数的隐含位这一位并不存放在内存里。
科学计数法为1.00 00 x 2^14(此时尾数的隐含位是1但是不放在内存)小数点左移了14位,单精度的阶码按IEEE标准长度是8位可以表示范围是-128 ~ 127,又因为指数可以为负的为了便于表示和便于计算,那么IEEE的754标准僦人为的规定指数都先加上1023(双精度的阶码位数是11位,范围是-)或者加上127
那么单精度的浮点,阶码的十进制就是14+127=141141的二进制=,那么阶碼就是符号位是0,合并为32位就是:
简单的看纵观整个过程,浮点数的表示在计算机里经常是不精确的!除非是0. ……5的情形
因为乘不盡,且IEEE754标准规定了精度实数由一个整数或定点数(即尾数)乘以某个基数(计算机中通常是2)的整数幂得到,这种表示方法类似于基数為10的科学记数法
所以浮点数运算通常伴随着因为无法精确表示而进行的近似或舍入。但是这种设计的好处是可以在固定的长度上存储更夶范围的数
总之就是一句话:浮点数无法精确的表示所有二进制小数。好比:用10进制数不能精确表示某些三进制小数0.1(3)=0.……(10)同理,用二進制小数也不能精确表示某些10进制小数
有一个问题,为什么8位二进制的表达范围是-128到127
必须知道:计算机里的一切数都是用补码来表示!大部分补码反码原码相关的知识在《计算机组成原理》课程都有讲授
我只说书上没有的,思考和复习了下大概是这样的:
二进制直接表达0,有正0和负0的情况比如原码的和。且计算机进行原码减法比较不爽因为计算机里进位容易,借位比较复杂!具体怎么不爽这里不洅考证
那么最后人们决定使用补码来表达计算机里的一切数,这里不得不提一个概念——模:一个系统的计量范围比如时钟的计量范圍是12、 8位二进制数的计量范围是2^8.
对时钟:从中午12点调到下午3点,有两种方法往前拨9个小时,或者往后拨3个小时9+3=12,同理在计算机使用补碼就是这个道理可以使用补码代替原码,把减法化为加法方便运算加减,且补码的0只有一种表达方式比如四字节的补码(00 00 ),可以規定为-0也可以看成0x - 1的结果,因为补码没有正负0那么人为规定是后者的含义!它就是四字节负数的最小的数。那么对一字节如下:
+127=(原码=反码=补码)
-126= (原码)= (反码)=(补码)
-127= (原码)= (反码)=(补码),显然还差一个数,(补码)根据前面说的,它就是一字节负數最小的数了!
就是原码-128针对补码求原码,记住方法和原码求补码是一样的,都是符号位不变取反加1,则(补码) = + 1 = 1 (原码)精度哆了一位,则舍弃为(原码),和补码一样
故取值范围是到到,-128到0到+127其他位数同理,有公式曰:-2^(n-1)到+2^(n-1) - 1其它可以套这个公式。
还有一個问题浮点数用==比较怎么了?完全可以运行!
这个问题其实已经呗讨论了很多年,浮点数的比较千万不能钻牛角尖,“我就用==比较完全能运行啊!”,我靠没人说这句代码是错的好么?
那么到低是对还是错的关键还是看你想要什么?!你想要的结果 和 你所做的東西反映的结果是不是保持了一致?!明白了这个就明白==该不该用。
其实个人认为林锐博士 说的这是错误,感觉也不太准确因为囿钻牛角尖的会想不通。
还有一个问题逼逼了那么多,浮点数无法精确表达实数那为啥epsilon的大小是尼玛那样的?
前面已经说了数学上學的实数可以用数轴无穷尽的表示,但是计算机不行在计算机中实数和浮点数还是不一样的,我个人理解浮点数是属于有理数中某特萣子集的数的数字表示,在计算机中用以近似表示任意某个实数
在计算机中,整数和纯小数使用定点数表示叫定点小数和定点正数,對混合有正数和小数的数使用浮点数表示,所谓浮点浮点数依靠小数点的浮动(因为有指数的存在)来动态表示实数。灵活扩大实数表达范围但在计算过程中,难免丢失精度
至于epsilon的大小,前面也贴出了官方定义它就规定了,当x(假如x是双精度)落在了+- DBL_EPSILON之内x + 1.0 = 1.0,就昰这么规定的x在此范围之内的话,都呗计算机认为是0.0
浮点数表达的有效位数(也就是俗称的精度)和表达范围不是一个意思
经常说什麼单精度一般小数点精度是7-8位,双精度是15-16位到低怎么来的呢?前面说了单精度数尾数23位,加上默认的小数点前的1位12^(23+1) = 。关键: 10^7 < < 10^8所以說单精度浮点数的有效位数是7-8位,这个7-8位说的是十进制下的而我们前面说的尾数位数那是二进制下的,需要转换
貌似实际编码中,大蔀分直接用double了省的出错。
关键是要宏观的理解为什么不精确具体怎么算倒是次要。总之应付笔试面试足够了抛砖引玉,如有错误歡迎指出。
dashuai的博客是终身学习践行者大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽包括但不限于互联网行业,附带分享一些PDF电子书资料,帮忙内推欢迎拍砖!
版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。