小数在计算机中的表示

整数在计算机中是可以精确表示的,任何一个整数都可以用二进制来准确表示,

但是小数却不行,举个例子:0.75=0.5+0.25(也就是2的-1次方与2的-2次方的和)

但0,76=0.5+0.25+........................,这是无法准确来表示的,只能无限接近0.76

在计算机里的小数是离散的!!!!!!

在计算机发展过程中,我们使用的小数和实数曾经提出过很多种的表示方法。典型的比如相对于浮点数的定点数(Fixed Point Number)。在这种表达方式中,小数点固定的位于实数所有数字中间的某个位置。货币的表达就可以使用这种方式,比如 88.22 或者 22.88 可以用于表达具有四位精度(Precision),小数点后有两位的货币值。由于小数点位置固定,所以可以直接用四位数值来表达相应的数值。SQL 中的 NUMBER 数据类型就是利用定点数来定义的。还有一种提议的表达方式为有理数表达方式,即用两个整数的比值来表达实数。

很显然,上面的定点数表示法有缺陷,不能表示很小的数或者很大的数。于是,为了解决这种问题,我们的前辈们自然想到了科学技术法的形式来表示,即用一个尾数(Mantissa ),一个基数(Base),一个指数(Exponent)以及一个表示正负的符号来表达实数。比如 123.456 用十进制科学计数法可以表达为 1.23456 × 102 ,其中 1.23456 为尾数,10 为基数,2 为指数。浮点数利用指数达到了浮动小数点的效果,从而可以灵活地表达更大范围的实数。

大约就在1985年,IEEE标准754的推出,它是一个仔细制定的表示浮点数及其运算的标准。这项工作是从1976年Intel发起8087的设计开始的,8087是一种为8086处理器提供浮点支持的芯片,他们雇佣了William Kahan,加州大学伯克利分校的一位教授,作为帮助设计未来处理器浮点标准的顾问。他们支持Kahan加入一个IEEE资助的制订工业标准的委员会。这个委员会最终采纳了一个非常接近于Kahan为Intel设计的标准。目前,实际上所有的计算机够支持这个后来被称为IEEE浮点(IEEE floating point)的标准。这大大改善了科学应用程序在不同机器上的可移植性。所谓IEEE就是电器和电子工程师协会。

介绍完了历史,先来看看浮点数最直接的表示。在数学上:

12.341010 = 1*101   +  2*100   +  3*10-1   +  4*10-2   = 12(34/100) (这里由于编辑器的原因,只能写这么机械了)。

在比如二进制:

101.112 = 1*22 + 0*21 + 1*20 + 1*2-1 + 1*2-2 = 4 + 0 + 1 + 1/2 + 1/4 = 5(3/4)。

上面简单的描述了在数学意义上的浮点数表示,但是在计算机中,我们存放在内存中的直观上看16进制数,那么这些16进制数是怎么表示我们浮点数的二进制形式呢?

在 IEEE 标准中,浮点数是将特定长度的连续字节的所有二进制位分割为特定宽度的符号域,指数域和尾数域三个域,其中保存的值分别用于表示给定二进制浮点数中的符号,指数和尾数。这样,通过尾数和可以调节的指数(所以称为"浮点")就可以表达给定的数值了。具体的格式:

符号位     阶码      尾数     长度
float             1         8       23      32
double          1        11       52      64

我们都知道浮点数在32位机子上有两种精度,float占32位,double占64位。很多朋友喜欢把double用于8字节的数据存储。从这点我们应该不要特殊看到浮点数的内存存储形式,他跟整数没有什么区别,只是在这4字节或者8字节里有3个区域,整数有符号只有符号位及后面的数值,之所以最高位表示有符号数的符号位。原因之一在于0x7fffffff位最大整数,为整个32位所能表示的最大无符号整数0xffffffff的一半减一,也就是:比如1字节:无符号是:0xff,有符号正数为:(0, 127],负数为[-128, 0)。在8位有符号时,肯定内存值大于等于: 0x80。二进制就是1000 0000,比他大,只会在低7位上变化,最高位已经是1了,变了就变小了。所以这里也是一个比较巧用的地方,一举两得。

那么,我们先来看32位浮点数 的换算:

1. 从浮点数到16进制数

float  var = 5.2f;

就这个浮点数,我们一步一步将它转换为16进制数。

首先,整数部分5,4位二进制表示为:0101。

其次,小数部分0.2,我们应该学了小数转换为二进制的计算方法,那么就是依次乘以2,取整数部分作为二进制数,取小数部分继续乘以2,一直算到小数结果为0为止。那么对0.2进行计算:

0.2*2 = 0.4 * 2 = 0.8 * 2 = 1.6(0.6) * 2 = 1.2(0.2)*2 = 0.4 * 2 = 0.8 * 2 = 1.6(0.6) * 2 = 1.2 ... ...

                0              0            1                     1                  0             0             1                  1   ... ...

因此,这里把0.2的二进制就计算出来了,结果就为:0.00110011... ... 这里的省略号是你没有办法计算完。二进制序列无限循环,没有到达结果为0的那一天。那么此时我们该怎么办?这里就得取到一定的二进制位数后停止计算,然后舍入。我们知道,float是32位,后面尾数的长度只能最大23位。因此,计算结束的时候,整数部分加上小数部分的二进制一共23位二进制。因此5.2的二进制表示就为:

101.00110011001100110011

一共23位。

此时,使用科学计数法表示,结果为:

1.0100110011001100110011 * 22

由于我们规定,使用二进制科学计数法后,小数点左边必须为1(肯定为1嘛,为0的话那不就是0.xxxx*sxxx 了,这样没有什么意义),这里不能为0是有一个很大的好处的,为什么?因为规定为1,这样这个1就不用存储了,我们在从16进制数换算到浮点数的时候加上这个1就是了,因为我们知道这里应该有个1,省略到这个1的目的是为了后面的小数部分能够多表示一位,精度就更高一些了哟。那么省略到小数点前面的1后的结果为:

.01001100110011001100110 * 22

这里后面蓝色的0就是补上的,这里不是随便补的一个0,而是0.2的二进制在这一位上本来就应该为0,如果该为1,我们就得补上一个1.是不是这样多了一位后,实际上我们用23位表示了24位的数据量。有一个位是隐藏了,固定为1的。我们不必记录它。

但是,在对阶或向右规格化时,尾数要向右移位,这样被右移的尾数的低位部分会被丢掉,从而造成一定的误差,因此要进行舍入处理。 常用的舍入方法有两种:一种是“0舍1入”法,即如果右移时被丢掉数位的最高位为0则舍去,为1则将尾数的末位加“1”,另一种是“恒置1”,即只要数位被移掉,就在尾数的末位恒置“1”。

举个例子:

123.456的二进制表示:

123.456的二进制到23位时:111 1011.0111 0100 1011 1100 01...

后面还有依次为01...等低位,由于最高位的1会被隐藏,向后扩展一位如果不做舍入操作则结果为:

1.11 1011 0111 0100 1011 1100 0 * 26

但是经过舍入操作后,由于被舍掉的位的最高位是1,或者“恒置1”法,最后面的0都应该是1。因此最终就应该是:

1.11 1011 0111 0100 1011 1100 1 * 26

在这里需要说明,不管是恒置1,还是0舍1入法,其根本都是为了减小误差。

好了,尾数在这里就计算好了,他就是 01001100110011001100110 

再来看阶数,这里我们知道是2^2次方,那么指数就是2。同样IEEE标准又规定了,因为中间的 阶码在float中是占8位,而这个 阶码又是有符号的(意思就是说,可以有2^-2次方的形式)。

float 类型的 偏置量 Bias = 2k-1 -1 = 28-1 -1 = 127 ,但还要补上刚才因为左移作为小数部分的 2 位(也就是科学技术法的指数),因此偏置量为 127 + 2=129 ,就是 IEEE 浮点数表示标准:

V = (-1)s × M × 2E

E = e - Bias

中的 e ,此前计算 Bias=127 ,刚好验证了 E = 129 - 127 = 2 。

这里的阶码就是12910 ,二进制就是:1000 00012 。

因此,拼接起来后:

1000 0001 01001100110011001100110

| ←   8位 → | | ←------------- 23位 -------------→ |

一共就是31位了,这里还差一位,那就是符号位,我们定义的是5.2,正数。因此这里最高位是0,1表示负数。

而后结果就是:

  0 1000 0001 01001100110011001100110

1位 | ← 8位 → | | ←-------------- 23位 ------------→ |

到这里,我们内存里面的十六进制数产生了,分开来看:

0 100 0000 1 010 0110 0110 0110 0110 0110

    4       0        A        6       6        6        6        6

因此,我们看到的就是0x40A66666, 此就是5.2最终的整数形式。

2.从十六进制数到浮点数

我们还是可以用上面5.2的例子,再将0x40A66666换算回去,用同样一个例子,结果更直观,逆运算更好理解。那我们就开始吧。

首先,要还原回去,必须将这个16进制用我们的计算器换算成二进制:

0 100 0000 1 010 0110 0110 0110 0110 011 0

我是COPY上面的。这里颜色已经很明显了,我划分成了3个区域 。 

首先确定符号,这里是0,因此是正数。

其次看绿色的8位,换成10进制就是:12910

我们逆运算,知道这里需要129 - 127 = 2得到指数,得到了指数,我们便知道我们小数点是向哪个方向移动了好多位。脑子里已经有了一个科学计数法的锥形。

再次把红色的23位提取出来,这里不把它换成10进制,因为我们指数是表示的二进制上移动了多少位,底数是2,而不是10。

这里因为之前我们都知道有个固定的1给省略了,因此这里要给加上去。加上去之后:

1 010 0110 0110 0110 0110 011 0

这里是24位,我们先不管,小数点添进去:

1 . 010 0110 0110 0110 0110 011 0 * 22 

然后将科学计数法变换成普通的二进制小数:

1 01 . 0 0110 0110 0110 0110 011 0

到这里,就真正可以把整数部分换成十进制了:

1 01 . 0 0110 0110 0110 0110 011 0

5.  xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

我们知道了,整数部分是5,后面的小数部分再进行逆运算:

这里我们就应该想想小数到二进制数是乘法,这里逆运算就应该除以2,因此就可以表示为:

0 .    0 0110 0110 0110 0110 011 0

0 + 0*2-1 + 0*2-2 + 1*2-3 + 1*2-4 + 0*2-5 + 0*2-6 + 1*2-7 + ... ... + 0*2-21 这样一个式子,我们算出结果来,放在浮点数里:

5.1999998。

因此我们可以看到精度已经有损失了。

问题一:写写-5.2的16进制数?

再来看一个例子:

float var = 0.5, 算16进制数。

首先,0.5整数部分为0,这里就不处理了。

其次,0.5小数部分,二进制表示为:0.1

这里是0.1,将尾数补满23位则是:

0.10 0000 0000 0000 0000 0002

由于小数点左边是0,因此需要向右移动一位 ,因此:

1.0 0000 0000 0000 0000 00002 * 2-1

这里1又被省略掉,所以23位全部变成了0 ,因此:

.00 0000 0000 0000 0000 00002 * 2-1

然后,因为这里指数是-1,因此阶码就是:-1 + 127 = 126 = 0111 11102

这样一来,阶码就有了,由于又是正数,那么组合起来:

0 01111110 00000000000000000000000

这样一来,最终的16进制数则为:0x3f000000.

是不是很简单啊。

 

64位浮点数 的换算:

这里就不再具体说明怎么换算的了,只需要提到2个地方:

一是,中间的阶码在double中占有11位,因此就不是+127了,而是加上1023,因为11位能表示的最大无符号数是2047,因此有符号范围[-1024, 1023]。

二是,尾数是52位,因此精度更高,能表示的数也就越大。我们在换算5.2的时候,后面的小数二进制+前面的5的二进制再省略一位后的总位数要填满52位。

好了,浮点数也没有太多要说的,就到这里吧,在用的时候注意精度和范围就可以了。

最后在提一个问题:

问题二:

float var0 = 5.2;

float var1 = 500.2;

float var2 = 50000.2;

float var3 = 5000000.2;

观察这几个数,加深一下那三个域的计算方式,并说出这些数据有什么规律?

原文传送门:请点击

原文地址:https://www.cnblogs.com/mlgjb/p/8318212.html

时间: 2024-08-06 00:39:00

小数在计算机中的表示的相关文章

小数在计算机中为什么会有误差?说明机器数、码制、浮点数、以及数制转换的一些问题

1.常用的数制 十进制  0~9 八进制 0~7 二进制 0~1 十六进制 0~F 2.十进制 转 各个进制 方法 : 除以基数取余反向 3. 2进制  8进制  16进制 转十进制 二进制 ___________________________ 2^3     2^2    2^1     2^0 1         0          0         1         X =8+ 1 = 9 八进制 ———————————-------------- 8^1    8^0 1    

小数在计算机中的存储形式

本篇的目的就是为了让更多的人了解浮点数存储的基本原理,还是那句话,学习的同时带着思考.同样这里不讨论浮点数的精度损失和数值的计算理论.直接讲实质的表现. 上节讲到,C语言中的小数可以使用指数形式来表示,即aEn或aen,它等价于a * 10n 在内存中,小数也是以指数形式来表示的,但又和C语言中的有所区别.小数在被存储到内存前,首先转换为下面的形式: a * 2n a 为尾数,是二进制形式,且 1 ≤ a < 2:n 为指数,是十进制形式. 其中,2 是固定的,不需要在内存中体现出来:正负号.指

计算机中的浮点数(IEEE754)

IEEE读作[a?-'tr?p(?)l-i:],电气和电子工程师协会.这个是一个包括所有电子和计算机技术的专业团体,制定标准是其工作之一. IEEE浮点数算术标准(IEEE 754)是最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用. 整数运算和浮点数运算会有不同的数学属性是因为它们处理数字表示有限性的方式不同——整数的表示算然只能编码一个相对较小的数字范围,但是这种表示是精确的:而浮点数虽然可以编码一个较大的数值范围,但是这种表示只是近似的. IEEE规定的浮点数编码会将一个浮点数转

数据在计算机中的存储

1.大小端模式(字节序) 大小端的概念:数在内存中分字节存储的先后顺序.大端模式即数的低位存在高地址,高位存在低地址当中.而小端模式相反,低位存在低地址,高位存在高地址.比如0x1234,这里的高低位是从左往右的.0x12是高位,0x34是低位.       1)大端模式: 低地址 -----------------> 高地址 0x12  |  0x34  |  0x56  |  0x78        2)小端模式: 低地址 ------------------> 高地址 0x78  | 

关于计算机中的《补码》,公式:-n=~n+1 引伸:~n=-n-1

在计算机系统中,数值一律用补码来表示(存储).主要原因是使用补码可以将符号位和其他位统一处理:同时,减法也可以按加法来处理.另外,两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃.补码跟源码的转换过程几乎是相同的. 补码概述 求给定数值的补码表示分以下两种情况: ⑴正数的补码 与 原码 相同. [例1]+9的补码是00001001.(备注:这个+9的补码说的是用8位的2进制来表示补码的,补码表示方式很多,还有16位2进制补码表示形式,以及32位2进制补码表示形式等.) ⑵负数的

IEEE 754 浮点数在计算机中的表示方法

IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用.这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number)),一些特殊数值(无穷(Inf)与非数值(NaN)),以及这些数值的“浮点数运算符”:它也指明了四种数值舍入规则和五种例外状况(包括例外发生的时机与处理方式). IEEE 754规定了四种表示浮点数值的方式:单精确度(32位).双精确度(64位).延伸单精确度(43比特以上,很少使

浮点数在计算机中存储方式

浮点数在计算机中存储方式 C语言和C#语言中,对于浮点类型的数据采用单精度类型(float)和双精度类型(double)来存储,float数据占用32bit,double数据占用64bit,我们在声明一个变量float f= 2.25f的时候,是如何分配内存的呢?如果胡乱分配,那世界岂不是乱套了么,其实不论是float还是double在存储方式上都是遵从IEEE的规范的, float遵从的是IEEE R32.24 ,而double 遵从的是R64.53. 无论是单精度还是双精度在存储中都分为三个

负数在计算机中是怎么存储

今天,发生一件非常有趣的事情. 公司同事问了我一个问题:为什么 2.0 - 1.1 = 0.89999999 呢?不应该是 0.9吗? 原来是,他问了周围一圈的同事,都给他的是同一个回答,说这是精度问题.他百思不得其解,怎么就会产生精度问题呢.再问,就没人知道原因了. 然后,我就看到了他抱着一本厚厚的书在看.拿过来一看,是一本Java书,厚厚的六百多页,这还仅是第一卷.哟呵,这是准备大干一场啊. 看在他这么努力学习的份上,还有他那对知识极度渴望的眼神.我决定,把我毕生所学传授与他. 于是,就给他

关于计算机中补码的问题

计算机中的有符号数有三种表示方法,即原码.反码和补码. 三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”: 而数值位,三种表示方法各不相同. 在计算机系统中,数值一律用补码来表示和存储. 原因在于,使用补码,可以将符号位和数值域统一处理,同时,加法和减法也可以统一处理. 接下来,来看一看原码.反码和补码到底是什么,以及它们之间的转换关系. 原码: 是一种计算机中对数字的二进制定点表示方法. 原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数该位为0,负数