‘壹’ 浮点数原理与精度损失问题
碰巧最近定义接口的时候碰到了浮点数精度的问题,稍微整理了浮点数的一些知识点:
浮点数的表示借鉴了科学计数法,比如在十进制中311.56可以表示成 。类似地,浮点型数据的二进制存储结构也可以被划分成:符号位 + 指数位 + 尾数位。按照国际标准IEEE 754,任意一个二进制浮点数可以表示成:
其中:
举两个简单的例子:
无论什么数据,在计算机内存中都是以01存储的,浮点数也不例外。
计算机中小数的表示按照小数点的位置是否固定可以分为浮点数和定点数。为了方便和float32浮点数做对比,我们构造一个32位精度的定点数,其中小数点固定在23bit处:
从定点数的存储上看,它表示的数值范围有限(以小数点在23bit为例,整数部分仅有8位,则整数部分取值范围是0~255),但好在处理定点数计算的硬件比较简单。
以32位浮点数为例,最高一位是符号位s,接着的8位是指数位E,最后的23位是有效数字M。double64最高一位是符号位,有11个指数位和52个有效数字位。下图展示了float32类型的底层表示:
其中IEEE 754的规定为:
以78.375为例,它的整数和小数部分可以表示为:
因此二进制的科学计数法为:
一般而言转换过程包括如下几步:
按照前面IEEE 754的要求,它的底层存储为:
十进制中的0.5(代表分数1/2)表示二进制中的0.1(等同于分数1/2),我们可以把十进制中的小数部分乘以2作为二进制的一位,然后继续取小数部分乘以2作为下一位,直到不存在小数为止。以0.2这个无法精确表示成二进制的浮点数为例:
因此十进制下的0.2无法被精确表示成二进制小数,这也是为什么十进制小数转换成二进制小数时会出现精度损失的情况。
从二进制小数的科学计数法表示上看,可以知道float的精度为 ,double的精度为 。
[1] https://q.115.com/182920/T1268124.html
[2] https://blog.csdn.net/u014470361/article/details/79820892
[3] https://www.cnblogs.com/wangsiting1997/p/10677805.html
[4] https://blog.csdn.net/u014470361/article/details/79820892
[5] https://www.cnblogs.com/yiyide266/p/7987037.html
‘贰’ 请问浮点型数据在计算机是怎么存储的
对于浮点类型的数据采用单精度类型(float)和双精度类型(double)来存储,float数据占用32bit,double数据占用64bit。
无论是单精度还是双精度在存储中都分为三个部分:
1、符号位(Sign) : 0代表正,1代表为负。
2、指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储。
3、尾数部分(Mantissa):尾数部分。
(2)浮点数存储原理csdn扩展阅读
实型变量分为两类:单精度型和双精度型,
其类型说明符为float 单精度说明符,double
双精度说明符。在Turbo
C中单精度型占4个字节(32位)内存空间,其数值范围为3.4E-38~3.4E+38,只能提供七位有效数字。
双精度型占8
个字节(64位)内存空间,其数值范围为1.7E-308~1.7E+308,可提供16位有效数字。
实型变量说明的格式和书写规则与整型相同。
例如: float x,y; (x,y为单精度实型量)
double a,b,c; (a,b,c为双精度实型量)
实型常数不分单、双精度,都按双精度double型处理。
‘叁’ 计算机浮点数的储存原理
浮点是以单元形式储存在内存上的,但每个单元内存有限,所以比如你想输入1/3的话,你以为是1/3了,实际上不足1/3,而是0.3333333333333333,所以计算时,会以0.3333333333333333的形式去计算,而不是1/3,因此出现了本来是0.6的,而输出却是0.599976.建议把浮点精度变大
‘肆’ 计算机组成原理里的:定点整数 定点小数 浮点数 编程里的基本数据类型int float在内存中的存储
整型就是一般的存储,有符号数,最高位为符号位,0表示正数,1表示负数。 无符号数,就没有什么格式了。
浮点数,就比较复杂了,它是遵守的IEEE754浮点编码标准,拿FLOAT类型来说,这种类型是32位的,其中1位表示符号位,8位表示指数位,23位表示有效数字位。 简单的用公式表示:(-1)^S * M ^e。 S是符号位,M是有效数字,E是指数,你最好自己去搜索一下IEEE754浮点数编码的内容。
当然这种知识了解一下就好了。。参考资料推荐 :深入理解计算机系统 我记得是第二章中,有详细的介绍。
‘伍’ C/C++浮点数在内存中是怎么存储的
把浮点数的绝对值的二进制表达的小数点移动到从左至右数第1个“1”之后,舍去1和小数点,把剩余的原码二进制0、1序列从左至右截取23(float型)或52(double型)位作为尾数。
在尾数前添加8(float型)或11(double型)位用移码表示“制造”尾数时小数点移动的位数,叫阶码;阶码的最左那一位表示小数点移动的方向。
在阶码前添加1位表示整个浮点数的正负,0表示大于等于0,1表示小于0。
把这一串0、1序列在小端机上由右至左存储在某个地址开始的连续内存单元中,这“某个地址”就是承载这个浮点型数据的变量的地址。若在大端机上则将这一串0、1序列由左至右存放。
‘陆’ 浮点型数据在内存中实际的存放形式(储存形式)
浮点型数据在内存中存储不是按补码形式,是按阶码的方式存储,所以虽然int和float都是占用了4个字节,如果开始存的是int型数据,比如是个25,那么用浮点的方式输出就不是25.0,也许就变的面目全非。
你可以用共用体的方式验证一下。在公用体中定义一个整形成员变量和一个浮点型成员变量,给整形赋值25,输出浮点成员变量,你就知道了。
‘柒’ float变量在内存当中是怎样存储的或是怎样的一种存储格式
浮点型变量在计算机内存中占用4字节(Byte),即32-bit。遵循IEEE-754格式标准。
一个浮点数由2部分组成:底数m 和 指数e。
±mantissa × 2exponent
(注意,公式中的mantissa 和 exponent使用二进制表示)
底数部分使用2进制数来表示此浮点数的实际值。
指数部分占用8-bit的二进制数,可表示数值范围为0-255。但是指数应可正可负,所以IEEE规定,此处算出的次方须减去127才是真正的指数。所以float的指数可从 -126到128.
底数部分实际是占用24-bit的一个值,由于其最高位始终为 1 ,所以最高位省去不存储,在存储中只有23-bit。
到目前为止, 底数部分 23位 加上指数部分 8位 使用了31位。那么前面说过,float是占用4个字节即32-bit,那么还有一位是干嘛用的呢? 还有一位,其实就是4字节中的最高位,用来指示浮点数的正负,当最高位是1时,为负数,最高位是0时,为正数。
浮点数据就是按下表的格式存储在4个字节中:
Address+0 Address+1 Address+2 Address+3
Contents SEEE EEEE EMMM MMMM MMMM MMMM MMMM MMMM S: 表示浮点数正负,1为负数,0为正数
E: 指数加上127后的值的二进制数
M: 24-bit的底数(只存储23-bit)
主意:这里有个特例,浮点数 为0时,指数和底数都为0,但此前的公式不成立。因为2的0次方为1,所以,0是个特例。当然,这个特例也不用认为去干扰,编译器会自动去识别。
通过上面的格式,我们下面举例看下-12.5在计算机中存储的具体数据:
Address+0 Address+1 Address+2 Address+3
Contents 0xC1 0x48 0x00 0x00 接下来我们验证下上面的数据表示的到底是不是-12.5,从而也看下它的转换过程。
由于浮点数不是以直接格式存储,他有几部分组成,所以要转换浮点数,首先要把各部分的值分离出来。
Address+0 Address+1 Address+2 Address+3
格式 SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM
二进制 11000001 01001000 00000000 00000000
16进制 C1 48 00 00
可见:
S: 为1,是个负数。
E:为 10000010 转为10进制为130,130-127=3,即实际指数部分为3.
M:为 10010000000000000000000。 这里,在底数左边省略存储了一个1,使用 实际底数表示为 1.10010000000000000000000
到此,我们吧三个部分的值都拎出来了,现在,我们通过指数部分E的值来调整底数部分M的值。调整方法为:如果指数E为负数,底数的小数点向左移,如果指数E为正数,底数的小数点向右移。小数点移动的位数由指数E的绝对值决定。
这里,E为正3,使用向右移3为即得:
1100.10000000000000000000
至次,这个结果就是12.5的二进制浮点数,将他换算成10进制数就看到12.5了,如何转换,看下面:
小数点左边的1100 表示为 (1 × 23) + (1 × 22) + (0 × 21) + (0 × 20), 其结果为 12 。
小数点右边的 .100… 表示为 (1 × 2-1) + (0 × 2-2) + (0 × 2-3) + ... ,其结果为.5 。
以上二值的和为12.5, 由于S 为1,使用为负数,即-12.5 。
所以,16进制 0XC1480000 是浮点数 -12.5 。
上面是如何将计算机存储中的二进制数如何转换成实际浮点数,下面看下如何将一浮点数装换成计算机存储格式中的二进制数。
举例将17.625换算成 float型。
首先,将17.625换算成二进制位:10001.101 ( 0.625 = 0.5+0.125, 0.5即 1/2, 0.125即 1/8 如果不会将小数部分转换成二进制,请参考其他书籍。) 再将 10001.101 向右移,直到小数点前只剩一位 成了 1.0001101 x 2的4次方(因为右移了4位)。此时 我们的底数M和指数E就出来了:
底数部分M,因为小数点前必为1,所以IEEE规定只记录小数点后的就好,所以此处底数为 0001101 。
指数部分E,实际为4,但须加上127,固为131,即二进制数 10000011
符号部分S,由于是正数,所以S为0.
综上所述,17.625的 float 存储格式就是:
0 10000011 00011010000000000000000
转换成16进制:0x41 8D 00 00
所以,一看,还是占用了4个字节。
下面,我做了个有趣的实验,就是由用户输入一个浮点数,程序将这个浮点数在计算机中存储的二进制直接输出,来看看我们上面所将的那些是否正确。
有兴趣同学可以到VC6.0中去试试~!
#include<iostream.h>
#define uchar unsigned char
void binary_print(uchar c)
{
for(int i = 0; i < 8; ++i)
{
if((c << i) & 0x80)
cout << '1';
else
cout << '0';
}
cout << ' ';
}
void main()
{
float a;
uchar c_save[4];
uchar i;
void *f;
f = &a;
cout<<"请输入一个浮点数:";
cin>>a;
cout<<endl;
for(i=0;i<4;i++)
{
c_save[i] = *((uchar*)f+i);
}
cout<<"此浮点数在计算机内存中储存格式如下:"<<endl;
for(i=4;i!=0;i--)
binary_print(c_save[i-1]);
cout<<endl;
}
好了,我想如果你仔细看完了以上内容,你现在对浮点数算是能比较深入的了解了。
‘捌’ 单片机里浮点数是怎么存放的
可以这么说:任何存储器,无论是pc机,单片机,甚至内存卡的基本存储模块都是一样
的结构(当然是对于ram而言),都是一个存储单元对应地址线的一种组合相应存储一个字节,物理结构是里面的八个触发器,每个触发器对应一个字节。至于浮点数和整型数理论上没什么区别了把,就在多一个字节存放小数点吧。
‘玖’ 说明用浮点数表示数据的原理
为了表示浮点数,数被分为两部分:整数部分和小数部分.例如,浮点数14.234就有整数部分14和小数部分0.234.首先把浮点数转换成二进制数,步骤如下:1把整数部分转换成二进制.2把小数部分转换成二进制.3在两部分之间加上小数点.浮点数还可以规范化,浮点数可以用单精度表示法和双精度表示法.规范化只存储这个数的三个部分的信息:符号,指教和尾数.如+1000111.0101规范化后为
+ 2^6 * 1.0001110101
符号 指数 尾数
规范化数的单精度表示法如+2^6*1.01000111001解:
由于符号为正,就用0表示.指数是6,在Excess_127表示法中,给指数加上127得到133.用二进制表示,就是10000101.尾数是01000111001.当把位数增加到32位,得到01000111001000000000000.注意不可以漏掉左边的0,因为它是小数.漏掉了那个0就相当于把这个数乘于2.这个数在内存中以32位数存储.如下所示
符号 指数 尾数
0 10000101 01000111001000000000000
‘拾’ 浮点类型是如何存储的
计算机中最小的存储单位是bit只能保存0和1,整数在内存中如何存储我们都知道,将要存储的数字转成2进制即可
用windows自带的计数器可以方便的查看整数对应的2进制值
如:
byte类型(单字节)
那浮点类型是如何用这么少的字节(如float 4字节)表示这么大(float 最大 3.4028235E38)的数字呢?
浮点数,是属于有理数中某特定子集的数的数字表示,在计算机中用以近似表示任意某个实数。具体的说,这个实数由一个整数或定点数(即尾数)乘以某个基数(计算机中通常是2)的整数次幂得到,这种表示方法类似于基数为10的科学计数法。
科学计数法是一种记数的方法。把一个数表示成a与10的n次幂相乘的形式(1≤|a|<10,a不为分数形式,n为整数),这种记数法叫做科学计数法。当我们要标记或运算某个较大或较小且位数较多时,用科学计数法免去浪费很多空间和时间。
这也是一种目前最常用的浮点数标准!为许多CPU与浮点运算器所采用。
简单的说就是将一个浮点数字拆成3个部分(符号部分、指数部分、小数部分) 存储在连续的bit中,类似科学计数法。
用 {S,E,M}来表示一个数 V 的,即 V =(-1)S × M × 2E ,如下:
其中:
其中d.dd...d 为有效数字,β为基数,e 为指数
有效数字中 数字的个数 称为 精度 ,我们可以用 p 来表示,即可称为 p 位有效数字精度。
每个数字 d 介于 0 和基数 β 之间,包括 0。
对十进制的浮点数,即基数 β 等于 10 的浮点数而言,上面的表达式非常容易理解。
如 12.34,我们可以根据上面的表达式表达为:
1×10 1 + 2×10 0 + 3×10 -1 + 4×10 -2
其规范的浮点数表达为: 1.234×10 1 。
但对二进制来说,上面的表达式同样可以简单地表达。
唯一不同之处在于:二进制的 β 等于 2,而每个数字 d 只能在 0 和 1 之间取值。
如二进制数 1001.101 ,我们可以根据上面的表达式表达为:
1×2 3 + 0×2 2 + 0×2 1 + 1×2 0 + 1×2 -1 + 0×2 -2 + 1×2 -3
其规范浮点数表达为: 1.001101×2 3 。
二进制数 1001.101 转成十进制如下:
由上面的等式,我们可以得出:
向左移动二进制小数点一位相当于这个数除以 2,而向右移动二进制小数点一位相当于这个数乘以 2。
如 101.11 = 5又3/4 (5.75),向左移动一位,得到 10.111 = 2又7/8 (2.875)。
除此之外,我们还可以得到这样一个基本规律:
一个十进制小数要能用浮点数精确地表示,最后一位必须是 5(当然这是必要条件,并非充分条件)。
如下面的示例所示:
基本换算方法:
将10进制的数拆分成整数和小数两个部分
整数部分除以2,取余数;小数部分乘以2,取整数位。
示例:
将十进制 1.1 转成 二进制
整数部分:1
1
小数部分:0.1
二进制形式表示为:
1.000110011001100110011...
再加上整数1,约等于:
1.099609375
计算的位数越多越精确
注意:
二进制小数不像整数一样,只要位数足够,它就可以表示所有整数。
在有限长度的编码中,二进制小数一般无法精确的表示任意小数,比如十进制小数0.2,我们并不能将其准确的表示为一个二进制数,只能增加二进制长度提高表示的精度。
根据 IEEE 754 浮点“双精度格式”位布局。
如果参数是正无穷大,则结果为 0x7ff0000000000000L。
如果参数是负无穷大,则结果为 0xfff0000000000000L。
如果参数是 NaN,则结果为 0x7ff8000000000000L。
根据 IEEE 754 浮点“单一格式”位布局。
如果参数为正无穷大,则结果为 0x7f800000。
如果参数为负无穷大,则结果为 0xff800000。
如果参数为 NaN,则结果为 0x7fc00000。
这里以 double类型说明
将一个浮点数与上面的掩码进行与运算,即可得到对应的 符号位、指数位、尾数位 的值。
1.000110011001100110011...
所以存为:
0 01111111111 000110011001100110011...
根据 IEEE 754 规范
在二进制,第一个有效数字必定是“1”,因此这个“1”并不会存储。
单精和双精浮点数的有效数字分别是有存储的23和52个位,加上最左边没有存储的第1个位,即是24和53个位。
通过计算其能表示的最大值,换十进制来看其精度:
浮点运算很少是精确的,只要是超过精度能表示的范围就会产生误差。而往往产生误差不是因为数的大小,而是因为数的精度。
我自己理解为分两种情况(这个不一定是对)
通过上面的转换示例,我们知道小数的二进制表示一般都不是精确的,在有限的精度下只能尽量的表示近似值
值本身就不是精确的,再进行计算就很可能产生误差
输出:
0.1
原始值: 0 01111111011
指数:1019 -1023 = -4
二进制形式:
0.0001
0.2
原始值:0 01111111100
指数:1020 -1023 = -3
二进制形式:
0.00
0.3
原始值:0 01111111101
指数:1021 = -2
二进制形式:
0.00
二进制加法运算
这里用float验证,float最大的精度是8位数
对于不能精确的表示的数,采取一种系统的方法:找到“最接近”的匹配值,它可以用期望的浮点形式表现出来,这就是舍入。
对于舍入,可以有很多种规则,可以向上舍入,向下舍入,向偶数舍入。如果我们只采用前两种中的一种,就会造成平均数过大或者过小,实际上这时候就是引入了统计偏差。如果是采用偶数舍入,则有一半的机会是向上舍入,一半的机会是向下舍入,这样子可以避免统计偏差。而 IEEE 754 就是采用向最近偶数舍入(round to nearest even)的规则。
(这段是网上抄的)
这里以java语言示例,用大端的方式示例(网络序)
java中是以大端模式存储的,java对我们屏蔽了内部字节顺序的问题以实现跨平台!
实际在不同的cpu架构下,存储方式不同,我们常用的X86是以小端的模式存储的。
网络传输一般采用大端序,也被称之为网络字节序,或网络序。IP协议中定义大端序为网络字节序。
输出: