曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 8345|回复: 1
打印 上一主题 下一主题

浮点数(单精度浮点数与双精度浮点数)在计算机中的存储

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
跳转到指定楼层
楼主
发表于 2009-12-29 16:55:16 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
浮点数格式
浮点数格式使用科学计数法表示实数。科学计数法把数字表示为系数(coefficient)(也称为尾数(mantissa)),和指数(oxponent)两部分。比如 3.684*10^2。在十进制中,指数的基数为 10,并且表示小数点移动多少位以生成系数。每次小数点向前移动时,指数就递增;每次小数点向后移动时,指数就递减。

例如,25.92 可表示为 2.592 * 10^1,其中 2.592 是系数,值 10^1 是指数。必须把系数和指数相乘,才能得到原始的实数。另外,如 0.00172 可表示为 1.72*10^-3,数字 1.72 必须和 10^3 相乘才能获得原始值。

二进制浮点格式
计算机系统使用二进制浮点数,这种格式使用二进制科学计数法啊格式表示值。数字按照二进制格式表示,那么系数和指数都是基于二进制的,而不是十进制,例如 1.0101*2^2。

在十进制里,像 0.159 这样的值,表示的是 0 + (1/10) + (5/100) + (9/1000)。相同的原则也适用二进制。比如,1.0101 乘以 2^2 后,生成二进制值 101.01 ,这个值表示二进制整数 5,加上分数 (0/2) + (1/4) 。这生成十进制值 5.25 。下表列出几个二进制小数以及它们对应的十进制值:
二进制
十进制分数
十进制值
0.1
1/2
0.5
0.01
1/4
0.25
0.001
1/8
0.125
0.0001
1/16
0.0625
0.00001
1/32
0.03125
0.000001
1/64
0.015625

几个二进制浮点例子:
二进制
十进制分数
十进制值
10.101
2+1/2+1/8
2.625
10011.001
19+1/8
19.125
10110.1101
22+1/2+1/4+1/16
22.8125
1101.011
13+1/4+1/8
13.375



编写二进制浮点值时,二进制通常被规格化了。这个操作把小数点移动到最左侧的数位,并且修改指针进行补偿。例如 1101.011 变成 1.101011*2^3

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
沙发
 楼主| 发表于 2009-12-29 18:05:12 | 只看该作者

浮点数的存储

IEEE 标准754 浮点数标准使用 3 个成分把实数定义为二进制浮点值:
      符号
      有效数字
      指数
符号位表示值是负的还是正的。符号位中的 1 表示负值,0 表示正值。

有效数字部分表示浮点数的系数(coefficient)(或者说尾数(mantissa))。系数可以是规格化的(normalized),也可以是非规格化的(denormalized)。当二进制值被规格化时,它写为小数点前有个1。指数被修改,表示移动了多少位才可以实现规格化(和科学计数法的方法类似)。这意味着,在规格化的值中,有效数字永远由 1 和二进制小数构成。

指数表示浮点数的指数部分。因为指数值可以是正值,也可以是负值,所以通过一个偏差值对它进行置偏。这样确保指数字段只能是无符号整数。这还限制了这种格式中可用的最小和最大指值。二进制浮点数的一般格式如下图所示:


浮点数的这 3 个部分被包含在固定长度的数据格式之内。IEEE 标准754 定义了浮点数的两种长度: 32位单精度 和 64位双精度

可以用于表示有效数字的位的数量决定精度。下图显示了两种不同精度类型的位布局:

单精度浮点使用 23 位有效数字值。但是,浮点格式假设有效数字的整数部分永远为 1 ,并且不在有效数字值中使用它。这样实际上有效数字的精度达到了 24 位。指数使用 8 位值,它的范围从 0~255,称为移码指数,意思是必须从指数中减去一个数(称为偏移量或者是偏差值),对单精度浮点数而言,这个值是 127 。也就是说,指数的范围是从 -126 到 +127 (二进制指数),这样整个浮点数的范围则为:(1.18 * 10^-38 到 3.40 * 10^38)

指数0和255用于特殊用途,。如果指数从1变化到254,则由s(符号位)、e(指数)和f(有效数)来表示的数为:




-1的 s 次幂是数学上的一种方法,意思是“如果 s 为0,则数是正的(因为任何数的 0 次幂等于 1 );如果 s 为 1,则数是负的(因为 -1的 1 次幂为 -1)”。

表达式的另一部分是1.f,意思是1后面为二进制小数点,再后面为23位的有效小数部分。它乘以2的幂,其中指数为内存中的8位移码指数减去127。

注意,还有一种特殊的情况 0 :
      如果 e 等于 0,且 f 等于 0,则数为 0。通常,所有32位均为 0 则表示 0。但是符号位可以是 1,在这种情况下,数被解释为-0。-0 可以表示一个很小的数,小到在单精度格式中不能用数字和指数来表示。尽管如此,它们然小于 0。
      如果 e 等于 0,且 f 不等于0,则数是有效的。但是,它不是规格化的数,它等于注意,二进制小数点左边的有效数为0。
      如果e等于255,且f等于0,则数为正或负无穷大,这取决于符号s。
      如果e等于255,且f不等于0,该值被认为“不是一个数”,简写为NaN。NaN可以表示一个不知道的数或者一个无效操作的结果。
Q:3.40 * 10^38 是值怎么来的?
A : 在单精度浮点格式中可以表示的最大规格化的正或负二进制数为:

换算成 10 进制约等于:3.402823669e+38,这里 1.111...111 近似为 2,则 2 * 2^127 = 2^128 = 3.402823669e+38 。

Q:1.18 * 10^-38 的值是怎么来的?
A:通常,单精度浮点格式中可以表示的最小规格化的正或负二进制数为:

换算成 10 进制就是:1.175494351e-38,也就是约等于 1.18 * 10^-38 。

Q:单精度浮点为换算为十进制后,为什么精度是 7 位?
A:0位二进制数近似等于3位十进制数。也就是说,若10位都置1(即十六进制为3FFh,十进制为1023),则它近似等于3位十进制都设置为9,即999。或者:
这种关系表明按单精度浮点格式存放的24位二进制数大约与7位十进制数等效。因此,也可以说单精度浮点格式提供24位二进制精度,或大约7位十进制精度。
可以这么设,一个 2 相当于 10^k 次方,即 10^k=2。那么 2 的 24 次方 2^24 = 10^24k 。从 10^k=2 可以知道 k = log(10)<2>(10为底,2为真数)。所以,2 的 24 次方换算到十进制,相当于有 24*log(10)<2> 约等于 7.2 个精度 。

Q:262144.00 和 262144.01 是一样的么?
A:当然不是一样的!但是在计算机里,单精度的存储中,它们却是一样的!看这两个数作为单精度浮点数时在计算机里是怎么存储的:
(gdb) x/4xb &value2
0x8049094 <value2>:     0x00    0x00    0x80    0x48
(gdb) x/4xb &value3
0x8049098 <value3>:     0x00    0x00    0x80    0x48
上面是一段汇编程序的在 GDB 中的调试代码,value2 和 value3 两个标号处分别
262144.00262144.01 。由此可见,两者的存储着同一个数字:。那这是为什么呢?原因是,规格化单精度浮点里,在小数点后有 23 位数,而 1.000...000 会经过 2^18 次方的运算后小数点会往前移动 18 个,那么小数点后面就只剩下 5 位,这时即使是 1/(2^5)=0.03125 都要比 0.01大,所以没办法,只能存为一样的数。

那么上面的问题如何避免呢?
答案是,使用双精度浮点数。

双精度浮点数的指数偏移量为1023,即3FFh,所以,以这种格式存放的数为

它具有与单精度格式中所提到适用于0、无穷大和NaN等情形相同的规则。

最小的双精度浮点格式的正数或负数为:

最大的数为:

用十进制表示,它的范围近似为 。10的308次幂是一个非常大的数,在1后面有308个十进制零。

53位有效数(包括没有包含在内的那1位)的精度与16个十进制位表示的精度十分接近。相对于单精度浮点数来说这种表示要好多了,但它仍然意味着最终还是有一些数与另一些数是相等的。例如,140737488355328.00与140737488355328.01是相同的,这两个数按照64位双精度浮点格式存储,结果都是:
42E0000000000000h
可把它转换为:


现在,在一小段的汇编程序里,声明两个浮点数,然后再来看一下它们在内存中的存储方式:
value4:
   .double 262144.00
value5:
   .double 262144.01
先查看一下 value5 处的存储情况:
(gdb) x/gf &value5
0x80490a4 <value5>:     262144.01000000001
由上面可以看到,在双精度的浮点下,整数部分+小数部分的位数一共有 17 位。

然后查看内存:
0x804909c <value4>:     0x00    0x00    0x00    0x00    0x00    0x00    0x10    0x41
(gdb) x/8xb &value5
0x80490a4 <value5>:     0xa4    0x70    0x3d    0x0a    0x00    0x00    0x10    0x41
第一个数是 262144.00 很容易看,那第 2 个如果要换算成 10 进制,那是多少呢?可以很有耐心的把 8 个十六进制数展开成二进制,然后向根据指数部分的运算(1041-1023=18),像前移动 18 位后,我们来算一下剩下来的二进制转换成十进制后是多少,由于手工用计算器算比较容易出错,我就写了个小的程序进行运算:
#include <stdio.h>
#include <math.h>

int main()
{
        double total = 0;
        int i;

        total += 1 / pow(2, 7);
        total += 1 / pow(2, 9);

        for (i = 13; i < 17; i++)
                total += 1 / pow(2, i);

        total += 1 / pow(2, 18);
        total += 1 / pow(2, 20);
        total += 1 / pow(2, 21);
        total += 1 / pow(2, 22);
        total += 1 / pow(2, 27);
        total += 1 / pow(2, 29);
        total += 1 / pow(2, 32);

        printf ("%lf\\n", total);

        return (0);
}
运行输出:
beyes@beyes-groad:~/programming/assembly/float$ ./test.exe
0.010000
从上面的结果看到,貌似和查看内存时少了后面一截。如果将 printf() 里面的 %lf 改成 %.17lf 就会看到输出:
beyes@beyes-groad:~/programming/assembly/float$ ./test.exe
0.01000000000931323
上面的输出在 0.010000000009 四舍五入后为 0.01000000001 。
之前之所以那么输出,这是由于 printf() 打印出双精度的十进制是按照 17 位(包括整数部分)来打印的,而把第 18 位的 9 忽略掉,从而之看到有 8 位的输出(后面都是0,没必要输出)。printf() 的处理方式和 gdb 里的显示方式稍微有点不同,gdb 里会四舍五入,而 printf() 不会,这大概是 printf() 是打印给“用户”看的,而 gdb 则是给“开发人员”看的缘故:)。

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-3 12:58 , Processed in 0.080398 second(s), 21 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表