满足什么要求的十进制浮点数的二进制表示可以精确表达

从如何判断浮点数是否等于0说起——浮点数的机器级表示
题目中针对的0,对于浮点类型,具体指的是0.0,自然对于指针类型就是NULL,对于整型就是0,一些常见笔试面试题中常出现,不要较真,十分欢迎提出改进意见。
本文很大程度上收到林锐博士一些文章的启发,lz也是在大学期间读过,感觉收益良多,但是当时林锐也是说了结论,lz也只是知其然,而不知其所以然,为什么要那样写?为什么要这样用?往往一深究起来就稀里糊涂了,现在有幸还是继续读书,我发现了很多问题理解的还不透彻,亡羊补牢。
题目中针对的0,对于浮点类型,具体指的是0.0,自然对于指针类型就是NULL,对于整型就是0,一些常见笔试面试题中常出现,不要较真,十分欢迎提出改进意见。
本文很大程度上收到林锐博士一些文章的启发,lz也是在大学期间读过,感觉收益良多,但是当时林锐也是说了结论,lz也只是知其然,而不知其所以然,为什么要那样写?为什么要这样用?往往一深究起来就稀里糊涂了,现在有幸还是继续读书,我发现了很多问题理解的还不透彻,亡羊补牢。
int *d; double d;几个变量,经过一系列的计算之后,那么去判断这个四个变量是否等于0该怎么做?
很多菜鸟或者编程功底不扎实的就会出错,一些烂书,尤其国内的一部分大学教材,教授编程语言的书籍,比如谭xx的,都存在很多不规范的误导,甚至是错误,这样的地方简直太多了,并不是程序出了想要的正确结果,就算完事儿了。
一些类似我这样的读过几本经典书籍,看过一些经典技术手册,码过若干行的代码等等,就会说这还不简单,会类似的写出:
void isZero(double d)
if (d &= -DBL_EPSILON && d &= DBL_EPSILON)
//d是0处理
void isZero(int d)
if (0 == d)
//d是0处理
void isZero(int *d)
if (NULL == d)
//d是空指针处理
void isZero(bool d)
//d就认为是false 也就是0
没错,很多经典的教科书或者指南,一些技术类的讲义,都会这样教授。但是为什么要这样写?
可能一部分人就糊涂了,不知道咋回答,搞技术或者做学问不是诗词歌赋,结论经不起严谨的推敲就不能服众,不可以说,书上是这样写的,或者老师告诉我的,那样太low了。尤其是浮点数比较的问题,不只是0,类似的和其他的浮点数比较大小的问题也是一样的。
要解决这个疑惑,必须先理解计算机是如何表示和存储浮点数据的,期间参考了IEEE单双精度的规范文档,和MSDN的一些文档,以及《深入理解计算机操作系统》一书。
1、先看看双精度的伊布西龙(高等数学或者初等数学里的数学符号就是它,epsilon)的值是多少
printf("%.40lf", DBL_EPSILON);
折合为科学计数法:
2、再看一些例子
printf("%0.100f\n", 2.7);
printf("%0.100f\n", 0.2);
printf("%0.100f\n", sin(3.793 / 6));
这个计算结果不是0.5,而是:
printf("%0.100f\n", 0.0000001);
打印结果是:
这样的结果在不同机器或者编译器下,有可能不同,但是能说明一个问题,浮点数的比较,不能简单的使用==,而科学的做法是依靠EPISILON,这个比较小的正数(英文单词episilon的中文解释)。
EPSILON被规定为是最小误差,换句话说就是使得EPSILON+1.0不等于1.0的最小的正数,也就是如果正数d小于EPISILON,那么d和1.0相加,计算机就认为还是等于1.0,这个EPISILON是变和不变的临界值。
官方解释:
For EPSILON, you can use the constants FLT_EPSILON, which is defined for float as 1.e-07F, or DBL_EPSILON, which is defined for double as 2.. You need to include float.h for these constants. These constants are defined as the smallest positive number x, such that x+1.0 is not equal to 1.0. Because this is a very small number, you should employ user-defined tolerance for calculations involving very large numbers.
一般可以这样写,防止出错:
double dd = sin(3.793 / 6);
/*if (dd == 0.5)
{取决于不同的编译器或者机器平台……这样写,即使有时候是对的,但是就怕习惯,很容易出错。
if (fabs(dd - 0.5) & DBL_EPSILON)
//满足这个条件,我们就认为dd和0.5相等,否则不等
puts("ok");//打印了ok
为什么浮点数的表示是不精确的?(简单的分析,否则里面的东西太多了)
这得先说说IEEE(Institute of Electrical and Electronic Engineers )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....”。也就是说尾数的这个隐含位占了一位精度!且尾数的隐含位这一位并不存放在内存里。
----------------------------------------------------------------------------------------------------------------
则20000.4表示为二进制 = 100 00 .
科学计数法为1.00 00
x 2^14(此时尾数的隐含位是1,但是不放在内存)小数点左移了14位,单精度的阶码按IEEE标准长度是8位,可以表示范围是-128 ~ 127,又因为指数可以为负的,为了便于表示和便于计算,那么IEEE的754标准就人为的规定,指数都先加上1023(双精度的阶码位数是11位,范围是-)或者加上127。
那么单精度的浮点,阶码的十进制就是14+127=141,141的二进制=,那么阶码就是,符号位是0,合并为32位就是:
尾数的小数点左边的1不存入内存)
简单的看,纵观整个过程,浮点数的表示在计算机里经常是不精确的!除非是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的大小是尼玛那样的?
1 #define DBL_EPSILON
2.3131E-16
2 #define FLT_EPSILON
3 #define LDBL_EPSILON
前面已经说了,数学上学的实数可以用数轴无穷尽的表示,但是计算机不行,在计算机中实数和浮点数还是不一样的,我个人理解。浮点数是属于有理数中某特定子集的数的数字表示,在计算机中用以近似表示任意某个实数。
在计算机中,整数和纯小数使用定点数表示,叫定点小数和定点正数,对混合有正数和小数的数,使用浮点数表示,所谓浮点,浮点数依靠小数点的浮动(因为有指数的存在)来动态表示实数。灵活扩大实数表达范围。但在计算过程中,难免丢失精度。
至于epsilon的大小,前面也贴出了官方定义,它就规定了,当x(假如x是双精度)落在了+- DBL_EPSILON之内,x + 1.0 = 1.0,就是这么规定的。x在此范围之内的话,都呗计算机认为是0.0 。
浮点数表达的有效位数(也就是俗称的精度)和表达范围不是一个意思
经常说什么单精度一般小数点精度是7-8位,双精度是15-16位,到低怎么来的呢?前面说了,单精度数尾数23位,加上默认的小数点前的1位1,2^(23+1) = 。关键: 10^7 &
& 10^8,所以说单精度浮点数的有效位数是7-8位,这个7-8位说的是十进制下的,而我们前面说的尾数位数那是二进制下的,需要转换。
又看,双精度的尾数52位存储,2^(52+1) = 0992,那么有10^16 & 0992 & 10^17,所以双精度的有效位数是16-17位。
貌似实际编码中,大部分直接用double了,省的出错。
关键是要宏观的理解为什么不精确,具体怎么算倒是次要。总之应付笔试面试足够了。抛砖引玉,如有错误,欢迎指出。
辛苦的劳动,转载请注明出处,谢谢……
/kubixuesheng/p/4107309.html
版权声明:本文内容由互联网用户自发贡献,本社区不拥有所有权,也不承担相关法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至: 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
用云栖社区APP,舒服~
【云栖快讯】红轴机械键盘、无线鼠标等753个大奖,先到先得,云栖社区首届博主招募大赛9月21日-11月20日限时开启,为你再添一个高端技术交流场所&&
阿里云机器学习是基于阿里云分布式计算引擎的一款机器学习算法平台。用户通过拖拉拽的方式可视化的操作组件来进行试验,...
云上企业级一站式智能研发协同平台,为企业用户提供从需求、编码到测试、发布、反馈等端到端的持续交付服务。
通过机器学习和数据建模发现潜在的入侵和攻击威胁,帮助客户建设自己的安全监控和防御体系,从而解决因网络攻击导致企业...
为您提供简单高效、处理能力可弹性伸缩的计算服务,帮助您快速构建更稳定、安全的应用,提升运维效率,降低 IT 成本...
阿里云双11狂欢,不只是5折
Loading...用二进制如何表示浮点型数值
由于计算机只认识0、1二进制,所以与表示整数一样,浮点数值最终也都会被解释为二进制机器码,与整型不同的是,所有由计算机储存的浮点类型,都是通过运算转换为十进制的,所以都是高度近似值,并不可能100%精确。
1、用二进制如何表示浮点型数值
由于计算机只认识0、1二进制,所以与表示整数一样,浮点数值最终也都会被解释为二进制机器码,与整型不同的是,所有由计算机储存的浮点类型,都是通过运算转换为十进制的,所以都是高度近似值,并不可能100%精确。具体规则如下:
遵循Ieee754标准(IEEE二进位浮点数算术标准)
首位均是符号位,1代表负,0代表正。
3.除去首位,用来表示浮点型的二进制要需要划分为指数位和尾数位(也称作小数位)。
不同浮点类型的指数位和尾数位占用长度不一样。
二进制转换十进制的指数偏差为:2^(指数位长度-1)-1。
这里所说的指数位、尾数位是十进制转二进制运算的关键,以32位浮点为例,它由1位符号位,8位指数位以及23位尾数位组成,例下面这个32位浮点数:
其中:指数位:100 0010 1;尾数位:101 01 。
从上面可知一下结论:
符号位为:0,即正值。
32位浮点,故指数偏差值:2^(8-1)-1 = 127。
指数位为:100 0010 1,即十进制133。
尾数位为:101 01 。
下面我们根据以上结论来运算十进制值,步骤如下:
计算指数,指数位减去指数偏差值,即133-127=6
计算小数,首先为尾数位前面补充小数点以及隐藏位1得:1.101 01 ,而后右移指数6位得: 00 1111
逐位运算,逐位求2的乘方得:
1*(2^6)+1*(2^5)+0*(2^4)+1*(2^3)+0*(2^2)+1*(2^1)+1*(2^0)+小数点+0*(2^-1)+0*(2^-2)+1*(2^-3)+0*(2^-4)+1*(2^-5)+0*(2^-6)+0*(2^-7)+0*(2^-8)+1*(2^-9)+1*(2^-10)+1*(2^-11)+0*(2^-12)+0*(2^-13)+1*(2^-14)+1*(2^-15)+1*(2^-16)+1*(2^-17)
=107.1597824
添加符号位得:+107.1597824
由此可知,浮点类型进制转换需要耗费一定的cpu运算,而且并不精确,如果想尽量精确,需要提升浮点类型的位数,例如64位。而且在一定范围外,不同的十进制浮点数可能会转换为相同的二进制浮点数,例如:
float f3 = 423.15243f;
float f4 = 423.15244f;
System.out.println(f3 == f4);
也就是说我们只能判断两个浮点数的精度差,一般使用f4 - f3 & 0.0001方式来判断两个浮点数是否相等(近似相等)。
2、中浮点型如何用二进制表示
在Java语言中,浮点数值分2种:float、double,均是带符号整型。
这些类型除了长度不一致外,其他规则均按照以上规则,具体如下:
内存中占用8个字节,32bit。其中符号位1位,指数位8位,尾数位23位。指数偏差值:2^(8-1)-1 = 127
public class Test {
public static void main(String[] args) throws UnsupportedEncodingException {
float f1 = 423.1594f;
float f2 = -423.1594f;
int floatToIntBits1 = Float.floatToIntBits(f1);// 根据IEEE754规则,得到浮点的表示值。
int floatToIntBits2 = Float.floatToIntBits(f2);
System.out.println("正float===" + Integer.toBinaryString(floatToIntBits1));// 转二进制
System.out.println("负float===" + Integer.toBinaryString(floatToIntBits2));
System.out.println("正float===" + Integer.toHexString(floatToIntBits1));// 转十六进制
System.out.println("负float===" + Integer.toHexString(floatToIntBits2));
结果如下:
正float===1100111
负float===
正float===43d39467
负float===c3d39467
内存中占用16个字节,64bit。其中符号位1位,指数位11位,尾数位52位。指数偏差值:2^(8-1)-1 = 1023
public class Test {
public static void main(String[] args) throws UnsupportedEncodingException {
double d1 = 7824345;
double d2 = -7824345;
long doubleToLongBits1 = Double.doubleToLongBits(d1);// 根据IEEE754规则,得到浮点的表示值。
long doubleToLongBits2 = Double.doubleToLongBits(d2);
System.out.println("正double===" + Long.toBinaryString(doubleToLongBits1));// 转二进制
System.out.println("负double===" + Long.toBinaryString(doubleToLongBits2));
System.out.println("正double===" + Long.toHexString(doubleToLongBits1));// 转十六进制
System.out.println("负double===" + Long.toHexString(doubleToLongBits2));
结果如下:
正double===011
负double===1011
正double===e01ab
负double===c119d874a39e01ab主题:63445
最新软件应用技术尽在掌握
浮点数为什么不精确?
1& &&&浮点数为什么不精确?
& && &&&其实这句话本身就不精确, 相对精确一点的说法是: 我们码农在程序里写的10进制小数,计算机内部无法用二进制的小数来精确的表达。
& &什么是二进制的小数? 就是形如 101.11&&数字,注意,这是二进制的,数字只能是0和1。
& &101.11 就等于 1 * 2^2 +0 *2^1 + 1*2^0 + 1*2^-1 + 1*2^-2&&= 4+0+1+1/2+1/4 = 5.75
& &下面的图展示了一个二进制小数的表达形式。
MJVRRrZ.jpg (7.31 KB, 下载次数: 9)
浮点数为什么不精确?
11:39 上传
& &&&(公众号码农翻身注: 此图来自于《深入理解计算机系统》第2章)
& &从图中可以看到,对于二进制小数,小数点右边能表达的值是 1/2, 1/4, 1/8, 1/16, 1/32, 1/64,&&1/128 ...&&1/(2^n)
& &现在问题来了, 计算机只能用这些个 1/(2^n) 之和来表达十进制的小数。
& &我们来试一试如何表达十进制的 0.2 吧。
& &0.01&&= 1/4&&= 0.25&&,太大
& &0.001 =1/8 = 0.125 , 又太小
& &0.0011& &= 1/8 + 1/16 = 0.1875 , 逼近0.2了
& &0.00111 = 1/8 + 1/16 + 1/32 = 0.21875&&, 又大了
& &0.001101 = 1/8+ 1/16 + 1/64 = 0.203125&&还是大
& &0.0011001 = 1/8 + 1/16 + 1/128 =&&0.1953125&&这结果不错
& &0. = 1/8+1/16+1/128+1/256 = 0.
& &已经很逼近了, 就这样吧。
& &这就是我说的用二进制小数没法精确表达10进制小数的含义。
& && && & 2& &&&浮点数的计算机表示
& && & 那计算机内部具体是怎么表示的呢?
& &计算机不可能提供无限的空间让程序去存储这些二进制小数。
& & 它需要规定长度,&&在Java 中, 提供了两种方式: float 和double , 分别是 32位 和 64位 。&&
& &可以这样查看一下一个float的内部表示(以0.09f为例):
& &Float.floatToRawIntBits(0.09f)&&
& &你将会得到:, 这是10进制的, 转化成二进制, 在前面加几个0补足 32位就是:
& &你可以看到它分成了3段:
& &第一段代表了符号(s)&&:&&0 正数,&&1 负数&&, 其实更准确的表达是 (-1) ^0&&
& &第二段是阶码(e): ,对应的10进制是 123
& &第三段是尾数(M)
& &你看到了尾数和阶码,就会明白这其实是所谓的科学计数法:
& &(-1)^s&&* M *&&2^e
& &对于0.09f 的例子,就是:
& &0 * (2^123)
& &好像不对,这肯定远远大于0.09f&&!
& & 这是因为浮点数遵循的是IEEE754 表示法, 我们刚才的s(符号) 是对的,但是 e(阶码)和 M(尾数) 需要变换:
& & 对于阶码e , 一共有8位, 这是个有符号数, 特别是按照IEEE754 规范,&&如果不是0或者255, 那就需要减去一个叫偏置量的值,对于float 是127&&
& &所以 E&&= e - 127 = 123-127 = -4
& &对于尾数M ,如果阶码不是0或者255,&&他其实隐藏了一个小数点左边的一个 1 (节省空间,充分压榨每一个bit啊)。
& &即 M = 1.
& &现在写出来就是:
& &1. * 2^-4
& &= 1/16 + 1/64 + 1/128+ 1/256 + ....& &
& &你看这就是0.09的内部表示, 很明显他比0.09更大一些, 是不精确的!
& &64位的双精度浮点数double是也是类似的, 只是尾数和阶码更长, 能表达的范围更大。
& &符号位 :1位
& &阶码 : 11位
& &尾数: 52位
ayAf2eE.jpg (13.87 KB, 下载次数: 2)
浮点数为什么不精确?
11:39 上传
& && & (公众号码农翻身注: 这个图也是来源于《深入理解计算机系统》第二章)
& &上面的例子0.09f 其实是所谓的规格化的浮点数, 还有非规格化的浮点数,这里就不展开了。
& && && & 3& && &&&使用浮点数& && && & 由于浮点数表示的这种“不精确性”或者说是“近似性”, 对于精确度要求不高的运算还行, 如果我们用float或者double 来做哪些要求精确的运算(例如银行)时就要小心了,&&很可能得不到你想要的结果。&&
& &具体的改进方法推荐大家看看《Effective Java》在第48条所推荐的“使用BigDecimal来做精确运算”。
& & 声明: 原创文章,版权所有,未经授权,禁止转载
& && & 你看到的只是冰山一角, 更多精彩文章,尽在码农翻身微信公众号:
& & 我是一个线程
& & 我是一个Java class
& & Javascript: 一个屌丝的逆袭
& & Java : 一个帝国的诞生
& & Basic : 一个老兵的自述
& & 小王的架构师之路
& & 程序员在工作中必备的能力
& & 码农需要知道的潜规则
& & TCP/IP 之 大明王朝的邮差
& & CPU 阿甘
& & IE为什么把Chrome和火狐打伤了
& & Node.js :我只需要一个店小二
& & 假如我是计算机系老师
& & 假如时光倒流,我会这么学Java
& & 学会编程,而不是学会Java
& & 15年编程生涯,资深架构师总结的7条经验
zaQR3ea.jpg (15.54 KB, 下载次数: 4)
浮点数为什么不精确?
11:39 上传
& && & 公共号:码农翻身
& & “码农翻身”公众号由工作15年的前IBM架构师创建,分享编程和职场的经验教训。
帖子永久地址:&<button type="submit" class="pn" onclick="setCopy('浮点数为什么不精确?\n/thread--1.html', '帖子地址已经复制到剪贴板您可以用快捷键 Ctrl + V 粘贴到 QQ、MSN 里。')">推荐给好友酷辣虫阅读提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!本内容由""投递,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。
width:100%">
我是火华哥,抢沙发专业户。。。。
width:100%">
我对楼主的敬仰如滔滔江水,绵延不绝!
width:100%">
俺从不写措字,但俺写通假字!&&
width:100%">
好,很好,非常好!
width:100%">
12345678910
| 粤公网安备 42号 )
Comsenz Inc.27282930311234567891011121314151617181920212223242528293031123456
随笔分类(4)
随笔档案(4)
文章分类(2)
文章档案(2)
阅读排行榜
评论排行榜

我要回帖

更多关于 4字节16进制转浮点数 的文章

 

随机推荐