IEEE754标准浮点数表示与舍入
原文地址:https://blog.fanscore.cn/p/26/
友情提示:本文排版不太好,但内容简单,请耐心观看,总会搞懂的。
1. 定点数
对于一个无符号二进制小数,例如101.111
,如果我们要用2个字节即16位来存储它,我们可以约定用高8位存储小数点前的数字,用低8位存储小数点后的数字,这样的话它在存储空间中就是这样的:00000101.11100000
。这种存储方式中小数点的位置是固定的,这称为定点数。这种存储方式有个问题那就是存储的数值是有上限的即11111111.11111111
= 27+26+25+24+23+22+21+20+2-1+2-2+2-3+2-4+2-5+2-6+2-7+2-8。如果我们要存储1111111111111111.
这个数的话,用这个存储方式是无法存储的,但是实际上对于这个数来说16位的存储空间是够用的,就是说定点数存在空间浪费的缺点。
基于这个缺点,计算机中通常用浮点数来表示一个小数。
2. 浮点数
IEEE754标准使用V = (-1)s × M × 2E表示浮点数,符号位(sign)s 决定该数是正数(s=0)还是负数(s=1),尾数(significand)M是一个二进制小数,阶码(exponent) E。
单精度浮点数中,s占用1位,M占用23位,E占用8位,总共32位,双精度浮点数s占1位,M占52位,E占11位,总共64位,这两种分别对应C中的float和double,另外还有一个扩展双精度它占用80位。
根据E值,浮点数有三种情况,
2.1 规格化的:E所有位既不全为0也不全为1。
在这种情况中,阶码被解释为以偏置(biased)形式表示的有符号整数,这时E的值表示为E=e-Bias,其中e为E所占位所表示的无符号整数,Bias=2E所占位数-1。举个单精度浮点数的,假设当前E为00001010
那么E = (00001010
所对应的无符号整数) - (28 - 1) = 10 - 127 = -117。
这种情况中M用来表示小数,其二进制表示为1.f-1f-2f-3……fn。举个单精度的例子,假设当前M为01100000000000000000100
,那么M=1 + (2-2 + 2-3 + 2-21)。
2.2 非规格化的:E所有位都为0
在这种情况中,阶码值E=1-Bias,而尾数M二进制表示为0.f-1f-2f-3……fn,没有规格化值前面的1。
非规格化值有两个用途。首先规格化值M始终>1,所以没法表示0,所以+0.0的浮点表示的位模式为全0:符号位0,阶码字段全为0(表明是一个非规格化值),尾数都是0就得到M=0.0。如果符号位为1,我们就得到了-0.0。其次非规格值的另外一个用途是表示那些非常接近0.0的数。
2.3 特殊值:E所有位都为1,这时又有两种以下两种情况
- 无穷大:M所有位全为0,当符号位为0是就是正无穷,当符号位为1时就表示负无穷。当我们把两个特别大的数相乘或者除0的时候无穷能表示溢出的结果。
- NaN(Not a Number):M不全为0,如果一些运算的结果不能是实数或者无穷,比如对-1开平方根时就会返回NaN。
经过上面的讲解后我们思考下十进制数15.3203125
使用单精度浮点数来表示的话其二进制形式应该是什么呢?我们首先将它转为二进制数,即:1111.0101001
= 1.1110101001
× 23,即M=1.1110101001
,E=3。
3. 浮点数舍入
浮点数并不能表示所有的实数,比如十进制的2.1没有完全对应的二进制数,浮点数只能近似的表示一些实数,为了尽量精确的表示这个实数就只能尽量增加二进制的位数,但是数据类型的位数是有限的,比如C中float只有32位。
关于十进制小数如何转二进制不清楚的同学可以自行搜索下相关文章,很简单,这里就不详述了。
这里举个例子:将十进制的2.1用单精度浮点数表示。首先小数点前的2转为二进制是10,然后我们将小数点后的0.1转为2进制,它是这个样子的:0.000110011001100110011001100110011001100110011001100110011...
(后面是0011无限循环)所以2.1转为二进制就是:10.000110011001100110011001100110011001100110011001100110011...
,转为IEEE标准表达方式就是
1.0000110011001100110011001100110011001100110011001100110011... × 21,即M=0.0000110011001100110011001100110011001100110011001100110011... + 1,但单精度浮点数位数只有23位,这样就面临一个问题00001100110011001100110(这里是23)01100110011001100110011001100110011...
这一长串23位之后的数字怎么办?直接舍去后面的位的话意味着计算机中所有小数都小于等于它的实际值,进1的话意味着计算机中所有小数都大于等于它的实际值,四舍五入看起来不错,但是由于中间的5会进位,所以仍然会使计算系统中的小数整体偏大。在进行一些大量数据的统计时,这三种方式都回累计一个相当大的误差。
IEEE浮点格式定义了四种不同的舍入方式,下面以十进制的小数舍入只保留小数点后0位为例:
方式 | 1.40 | 1.60 | 1.50 | 2.50 | -1.50 |
---|---|---|---|---|---|
向偶数舍入 | 1 | 2 | 2 | 2 | -2 |
向零舍入 | 1 | 1 | 1 | 2 | -1 |
向下舍入 | 1 | 1 | 1 | 2 | -2 |
向上舍入 | 2 | 2 | 2 | 2 | -1 |
向偶数舍入这个方式乍看可能没看懂,它其实是使舍入后的值的最低有效数字是偶数。1.5舍入有两个选择:1和2,但由于2是偶数所以就舍入到2,同样2.5舍入有两个选择:2和3,但由于3是奇数,所以还是舍入到2。
向偶数舍入的方式使得在大多数情况下,5舍去还是进位的概率是差不多的,在进行一些大量数据的统计时产生的偏差相较其他方式小一些。
4. 二进制舍入的与规则总结
好多中文资料一般到这里就戛然而止了,CSAPP书中讲到这也没有给到一个二进制的例子,相信大部分读者看完了上面也不知道二进制里是怎么处理的,所以下面给个二进制舍入的例子。
假设我们要求只保留小数点后三位,有以下例子:
1.001 011
舍入后:1.001
原因:1.001 011
舍入有两个选择:1.001
和1.010
,|1.001 011 - 1.001| = 0.000 011
,|1.001 011 - 1.010| = 0.000 101
,显然0.000 011
<0.000 101
,所以1.001
比1.010
更接近原值1.001 011
,所以舍入到了1.001
1.001 101
舍入后:1.010
原因:1.001 101
舍入有两个选择:1.001
和1.010
,|1.001 101 - 1.001| = 0.000 101
,|1.001 101 - 1.010| = 0.000 011
,显然0.000 101
>0.000 011
所以舍入到后者。1.001 100
舍入后:1.010
原因:1.001 100
舍入有两个选择:1.001
和1.010
,|1.001 100 - 1.001| = 0.000 100
,|1.001 100 - 1.010| = 0.000 100
,两种选择的差值是相同的,这时使用向偶数舍入的方式,1.010
是偶数(0偶1奇),所以舍入到1.010
根据上面的例子我们总结出以下规律:
我们用RR...RDD...D来表示一个二进制小数,R表示保留位,D表示舍去位,那么有以下规则:
- DD...D < 10...0 直接舍去
- DD...D > 10...0 向上舍入
- DD...D = 10...0 向偶数舍入,细则:
- RR...R = XX...0,直接舍去
- RR...R = XX...1,向上舍入
5. 代码验证下
最后,我们写一段C代码,看下到底是不是按照IEEE754标准存的浮点数,代码如下:
int main(void) {
float a = 2.1;
float b = a + 3;
return 0;
}
gcc编译下:
$ gcc -O0 -g float.c // -O0禁用优化,-g以下面使用gdb调试
gdb调试下:
$ gdb ./a.out
进入gdb后,输入start
再输入layout asm
查看反汇编结果:
可以看到a的值被存入了寄存器eax
,在gdb中通过i r eax
查看eax寄存器中的值:
可以看到eax寄存器中保存的值是0x400666666
,转为二进制:01000000000001100110011001100110
,套入IEEE754标准表示法:
0
10000000
00001100110011001100110
,即符号位为0,M = 1.00001100110011001100110
,E = 27 - (27 - 1) = 1
参考资料
IEEE754标准浮点数表示与舍入的更多相关文章
- 第二章 运算方法与运算器(浮点数的加减法,IEEE754标准32/64浮点规格化数)
这一章,主要介绍了好多种计算方法.下面,写一点自己对于有些计算(手写计算过程)的见解. 1.原码.反码.补码 原码:相信大家都会写,符号位在前二进制数值在后,凑够位数即可. 反码:原码符号位不变,其他 ...
- C#中浮点数依IEEE-754标准转二进制串 (MODBUS 浮点数转换)
因工作需要,把再串口通信中浮点数与字节流的数据转换函数放在这,转发的,谢谢原作者. 今天花了一天的时间搜罗资料,为了解决一个串口编程的进制转化问题.因为串口传送的浮点数据格式与IEEE-754标准(3 ...
- 十进制浮点数转换成IEEE754标准的32浮点数的二进制格式
参考: http://jimmygod.blog.163.com/blog/static/43511339200792605627411/ http://blog.csdn.net/archersab ...
- 将四个BYTE数值转换成IEEE754标准的浮点数(两种方法:用Addr函数取字节数字的首地址,或者用Absolute关键字)
在工作中,经常使用到IEEE754格式的数据.IEEE754格式的数据占四个字节,好像Motorola格式和Intel格式的还不一样. 由于工作中很少和他打交道(使用的软件内部已经处理),就没太在意. ...
- IEEE754标准浮点格式
两种基本浮点格式:单精度和双精度.IEEE单精度格式具有24位有效数字,并总共占用32 位.IEEE双精度格式具有53位有效数字精度,并总共占用64位 两种扩展浮点格式:单精度扩展和双精度扩展.此标准 ...
- IEEE754标准
以下计算按规格化规定: S:符号位 M:分数值 E:指数偏移值 单精度浮点数(32bit): NUM_single = (-1)^S * 1.M * 2^(E-127) 双精度浮点数(64b ...
- IEEE754标准的浮点数存储格式
操作系统 : CentOS7.3.1611_x64 gcc版本 :4.8.5 基本存储格式(从高到低) : Sign + Exponent + Fraction Sign : 符号位 Exponent ...
- IEEE754二进制浮点数算术标准
对于32位浮点数 sign: 符号,1位 exponent: 指数,8位,偏码 fraction: 分数,23位,原码 特殊值 指数域的编码值 = 指数的实际值 + 127 这样按照字典序的顺序就 ...
- 【转载】JS Number类型数字位数及IEEE754标准
JS的基础类型Number,遵循 IEEE 754 规范,采用双精度存储(double precision),占用 64 bit.如图 意义 1位用来表示符号位 11位用来表示指数 52位表示尾数 浮 ...
随机推荐
- Java学习的第十六天
1. 向上转型 向下转型 静态绑定 2.无问题 3.明天学习static关键字和final关键字
- Python常用组件、命令大总结(持续更新)
Python开发常用组件.命令(干货) 持续更新中-关注公众号"轻松学编程"了解更多. 1.生成6位数字随机验证码 import random import string def ...
- [Luogu P3203] [HNOI2010]弹飞绵羊 (LCT维护链的长度)
题面 传送门:洛谷 Solution 这题其实是有类似模型的. 我们先考虑不修改怎么写.考虑这样做:每个点向它跳到的点连一条边,最后肯定会连成一颗以n+1为根的树(我们拿n+1代表被弹出去了).题目所 ...
- IOCP 模型1
// IOCP.cpp : Defines the entry point for the console application. // // #include "stdafx.h&quo ...
- CentOS7下一键小白搭建seafile pro云盘
搭建前准备工作 vps或者云服务器,个人搭建使用建议腾讯云,公司搭建使用建议阿里云. 没有服务器的小伙伴可以下面链接进入看下,腾讯云的配置带宽会比阿里云的好点. 阿里云新人优惠服务器 腾讯云云上特惠 ...
- 腾讯云对象存储COS新品发布——智能分层存储,自动优化您的存储成本
近日,腾讯云正式发布对象存储新品--智能分层存储,能够根据用户数据的访问模式,自动地转换数据的冷热层级,为用户提供与标准存储一致的低延迟和高吞吐的产品体验,同时具有更低的存储成本. 熟悉数据存储的用户 ...
- Assert类的静态方法
五:常用断言 在NUnit中,断言是单元测试的核心.NUnit提供了一组丰富的断言,这些断言是Assert类的静态方法.如果一个断言失败,方法的调用不会返回值,并且会报告一个错误.如果一个测试包含多个 ...
- C# 泛型集合的自定义类型排序
一.泛型集合List<T>排序 经sort方法之后,采用了升序的方式进行排列的. List<int> list = new List<int>() { 2, 4, ...
- php执行exec、xsell_exec命令失败
在php.ini下进行更改 查找disable_function 去掉exec xsell_exec 重启php
- 日志切分神器--logrotate
Blog:博客园 个人 概述 还在自己写定时切分日志的脚本?试试系统自带的logrotate工具吧! logrotate是一个日志文件管理工具.用于分割日志文件,删除旧的日志文件,并创建新的日志文件, ...