计算机中的数分为整数与实数。对于实数,绝大多数现代的计算机系统采纳了所谓的浮 点数表达方式。 这种表达方式利用科学计数法来表达实数,即用一个尾数(Mantissa ), 一 个基数(Base),一个指数 e(阶码 E=e+127 或者 e+1023)(exponent)以及一个表示正负 的符号(Sign)来表达实数。 比如 123.45 用十进制科学计数法可以表达为 1.2345 × 10^2 , 其中 1.2345 为尾数,10 为基数,2 为指数。 浮点数利用指数达到了浮动小数点的效果, 从而可以灵活地表达更大范围的实数。 又对于一个二进制的数比如 1011.01,用科学计数 法也可以表示为:1.01101*2^3,其中 1.1101 为尾数,2 为基数,3 为指数。
一,浮点数的存储方法
计算机中是用有限的连续字节保存浮点数的。 保存这些浮点数当然必须有特定的格式, C/C++中的浮点数类型 float 和 double 采纳了 IEEE 754 标准中所定义的单精度 32 位 浮点数和双精度 64 位浮点数的格式。 在 IEEE 标准中,浮点数是将特定长度的连续字节 的所有二进制位分割为特定宽度的符号域,指数域和尾数域三个域, 其中保存的值分别用 于表示给定二进制浮点数中的符号,指数和尾数。 这样,通过尾数和可以调节的指数(所 以称为"浮点")就可以表达给定的数值了。
根据国际标准 IEEE 754,任意一个二进制浮点数 V 可以表示成下面的形式:
V = (-1) ^ s × M × 2 ^ E
(1)(-1)^s 表示符号位,当 s=0,V 为正数;当 s=1,V 为负数。
(2)M 表示有效数字,大于等于 1,小于 2,但整数部分的 1 不变,因此可以省略。
(3)2^E 表示指数位。
比如: 对于十进制的 5.25 对应的二进制为:101.01,相当于:1.0101*2^2。所以,S 为 0,M 为 1.0101,E 为 2。 而-5.25=-101.01=-1.0101 *2^2.。所以 S 为 1,M 为 1.0101,E 为 2。
对于 32 位的单精度数来说,从低位到高位,尾数 M 用 23 位来表示,阶码 E 用 8 位来表示, 而符号位用最高位 1 位来表示,0 表示正,1 表示负。对于 64 位的双精度数来说,从低位 到高位,尾数 M 用 52 位来表示,阶码用 11 位来表示,而符号位用最高位 1 位来表示,0 表示正,1 表示负。
IEEE 754 对有效数字 M 和指数 E,还有一些特别规定。 前面说过,M 可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。IEEE 754 规定,在计算机内部保存 M 时,默认这个 数的第一位总是 1,因此可以被舍去,只保存后面的 xxxxxx 部分。比如保存 1.0101 的时候, 只保存 0101,等到读取的时候,再把第一位的 1 加上去。这样做的目的,是节省 1 位有效 数字。以 32 位浮点数为例,留给 M 只有 23 位,将第一位的 1 舍去以后,等于可以保存 24 位有效数字。
对于 E, 首先,E 为一个无符号整数(unsigned int)。这意味着,如果 E 为 8 位,它的取 值范围为 0~255;如果 E 为 11 位,它的取值范围为 0~2047。然而科学计数法中的 E 是可 以出现负数的,所以 IEEE 754 规定,E 的真实值必须再减去一个中间数,对于 8 位的 E, 这个中间数是 127;对于 11 位的 E,这个中间数是 1023。 比如,2^2 的 E 是 2,所以保 存成 float 32 位浮点数时,必须保存成 2+127=129,即 10000001。
此外,E 还需要考虑下面 3 种情况:
(1)E 不全为 0 或不全为 1。这时,浮点数就采用上面的规则表示,即指数 E 的计算值减 去 127(或 1023),得到真实值,再将有效数字 M 前加上第一位的 1。
(2)E 全为 0。这时,浮点数的指数 E 等于 1-127(或者 1-1023),有效数字 M 不再加上 第一位的 1,而是还原为 0.xxxxxx 的小数。这样做是为了表示±0,以及接近于 0 的很小的 数字。
(3)E 全为 1。这时,如果有效数字 M 全为 0,表示±无穷大(正负取决于符号位 s);如 果有效数字 M 不全为 0,表示这个数不是一个数(NaN)。
二,浮点数的转换方法
浮点数的转换方法可以分为如下 2 种情况:
1.给出一个浮点数,计算对应的二进制 比如给定一个浮点数,7.25,如何计算它对应的单精度和双精度的二进制呢?
首先,十进制浮点数 7.25 对应的二进制(二进制,十进制和十六进制转化方法:点击这里) 为:111.01。用二进制的科学计数法为:1.1101*2^2。所以,按照上面浮点数的存储结构, 得出符号位为: 0,表示正数;阶码(指数) E 单精度为 2+127=129,双精度为 2+1023=1025; 小数部分 M 为:1101。 所以,
单精度的二进制位:0 10000001 1101 0000000000000000000;
双 精 度 的 二 进 制 位 : 0 10000000001 1101 000000000000000000000000000000000000000000000000
第一步:将 178.125 表示成二进制数:(178.125)(十进制数)=(10110010.001)(二进制形式);
第二步:将二进制形式的浮点实数转化为规格化的形式:(小数点向左移动 7 个二进制位可以 得到)
10110010.001=1.0110010001*2^7 因而产生了以下三项:
符号位:该数为正数,故第 31 位为 0,占一个二进制位.
阶码:指数(e)为 7,故其阶码为 127+7=134=(10000110)(二进制),占从第 30 到第 23 共 8 个 二进制位.
(注:指数有正负即有符号数,但阶码为正即无符号数,所以将 e 加个 127 作为偏移,方 便指数的比较)
尾数为小数点后的部分, 即 0110010001.因为尾数共 23 个二进制位,在后面补 13 个 0,即 01100100010000000000000
所以,178.125 在内存中的实际表示方式为:
0 10000110 01100100010000000000000
2.给出一个浮点数的二进制,计算对应的十进制值
而如果而如果给出了一个浮点数的二进制,如何计算它对应的十进制,其实就是 1 中的逆 运算。分别求出对应的符号位,阶码指数 E 和小数 M 部分,就可以了。比如,给定一个单 精度浮点数的二进制存储为: 0 10000001 1101 0000000000000000000; 那么对应的符号为:0,表示正数;阶码 E 为:129-127=2;尾数为 1.1101。所以对应的二 进制科学计数法为:1.1101*2^2,也就是 111.01 即:7.25。
小数的输出
小数也可以使用 printf 函数输出,包括十进制形式和指数形式,它们对应的格式控制符分别是:
%f 以十进制形式输出 float 类型;
%lf 以十进制形式输出 double 类型;
%e 以指数形式输出 float 类型,输出结果中的 e 小写;
%E 以指数形式输出 float 类型,输出结果中的 E 大写;
%le 以指数形式输出 double 类型,输出结果中的 e 小写;
%lE 以指数形式输出 double 类型,输出结果中的 E 大写。
对代码的说明:
-
%f 和 %lf 默认保留六位小数,不足六位以 0 补齐,超过六位按四舍五入截断。
-
将整数赋值给 float 变量时会变成小数。
-
以指数形式输出小数时,输出结果为科学计数法;也就是说,尾数部分的取值为:0 ≤ 尾数 < 10。
另外,小数还有一种更加智能的输出方式,就是使用%g。%g 会对比小数的十进制形式和指数形式,以最短的方式来输出小数,让输出结果更加简练。所谓最短,就是输出结果占用最少的字符。
%g 使用示例:
运行结果:
a=1e-05
b=3e+07
c=12.84
d=1.22934
对各个小数的分析:
-
a 的十进制形式是 0.00001,占用七个字符的位置,a 的指数形式是
1e-05,占用五个字符的位置,指数形式较短,所以以指数的形式输出。 -
b 的十进制形式是 30000000,占用八个字符的位置,b 的指数形式是 3e+07,占用五个字符的位置,指数形式较短,所以以指数的形式输出。
-
c 的十进制形式是 12.84,占用五个字符的位置,c 的指数形式是 1.284e+01,占用九个字符的位置,十进制形式较短,所以以十进制的形式输出。
-
d 的十进制形式是 1.22934,占用七个字符的位置,d 的指数形式是 1.22934e+00,占用十一个字符的位置,十进制形式较短,所以以十进制的形式输出。
读者需要注意的两点是:
- %g 默认最多保留六位有效数字,包括整数部分和小数部分;%f 和 %e 默认保留六位小数,只包括小数部分。
- %g 不会在最后强加 0 来凑够有效数字的位数,而 %f 和 %e 会在最后强加 0 来凑够小数部分的位数。
总之,%g 要以最短的方式来输出小数,并且小数部分表现很自然,不会强加零,比 %f 和 %e 更有弹性,这在大部分情况下是符合用户习惯的。
除了 %g,还有 %lg、%G、%lG:
- %g 和 %lg 分别用来输出 float 类型和 double 类型,并且当以指数形式输出时,e小写。
- %G 和 %lG 也分别用来输出 float 类型和 double 类型,只是当以指数形式输出时,E大写。