一、单精度浮点数
先来简单了解一下浮点数在计算机中的存储方式。根据IEEE 754标准,单精度浮点数格式如下(所有位取0):
符号位 |
指数部分 |
尾数 |
|||||||||||||||||||||||||||||
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
各部分解释
单精度浮点数有32个二进制位,左侧是高位,右侧是低位。最高位被指定为符号位,0代表正数,1代表负数。指数部分将是2的幂次,其编码值(即上表指数部分对应的八个二进制位)规定为指数的实际值加上偏移值2^7-1=127,这是为了避免负数,将[-127, 128]映射到[0, 255],这样指数部分编码就可以简单地编排为[00000000, 11111111]。例如指数部分为00001000,十进制为8。那么其所代表的实际指数是8-127=-119,即要乘上2-119。最后23位尾数是不包含整数位的实际有效小数位。规约数的整数位是1,非规约数的整数位是0。
规约形式的浮点数与非规约形式的浮点数
指数部分的编码值在[1, 2e-2]内,且尾数部分的整数位是1,这样的浮点数被称为规约形式的浮点数。
指数部分的编码值为0,尾数非零,这样的浮点数被称为非规约形式的浮点数。
规约浮点数的尾数∈[1, 2),而非规约浮点数的尾数∈(0, 1)。 需要注意,非规约数指数编码为00000000,但指数实际值是-126,而非-127。非规约浮点数被IEEE 754-1985标准采用是因为它的渐进式下溢出,而规约浮点数将导致突然式下溢出,具体原理不再展开。
实际计算
设符号位为s。sign(s)确定正负:sign(0)=1,sign(1)=-1;指数部分为e;尾数部分为f。用(N)2表示二进制数N。
规约形式:sign(s)*2e-127*(1.f)2
非规约形式:sign(s)*2-126*(0.f)2
特殊值和极值
类别 |
符号位 |
指数部分 |
尾数 |
数值 |
|||||||||||||||||||||||||||||
正负零 |
0/1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
±0 |
正负无穷 |
0/1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
±∞ |
最大规约数 |
0/1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
±(2−2-23) × 2127 ≈ ±3.40e38 |
最大非规约数 |
0/1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
±(1−2−23) × 2-126 ≈ ±1.18e-38 |
最小规约数 |
0/1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
±2−126 ≈ ±1.18e-38 |
最小非规约数 |
0/1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
±2−23 × 2−126 = ±2−149 ≈ ±1.40e-45 |
NaN |
0/1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
非零 |
NaN |
上下溢出值
由浮点数的存储方式可以看出计算机所能表示的浮点数是有限的,我们把所能表示的最大正值称为上溢值,而把最接近0的正值称为下溢值。由表二我们看到上溢值为±3.40e38,下溢值为±1.40e-45。
二、机器精度
Wikipedia上机器精度Machine Epsilon是这样描述的:“Machine epsilon gives an upper bound on the relative error due to rounding in floating point arithmetic”。因为浮点数是离散的,所以实数的表示存在着误差。例如圆周率这样的无限不循环小数不可能精确地由某一个浮点数表示。
我们需要一些具体的量去刻画这种误差,以估计结果的准确性。机器精度便是其中之一:它是所有相对误差的上限。相对误差是绝对误差与精确值的比值的绝对值。例如一个精确的实数x,所有单精度浮点数中与x距离最近的数为y,绝对误差为|y-x|,相对误差即(|y-x| over |x|),而所有相对误差的上限便是单精度浮点数的机器精度。
对于32位浮点数,指数8位,尾数为23位。对于两个指数实际值为E的相同的浮点数,若它们尾数部分相差(00000000000000000000001)2,即2-23,易见它们是相邻的。那么与它们指数相同的实数x与距x最近的浮点数y之间的距离|y-x|一定小于此相邻两浮点数的距离2-23 * 2E。可以取x=1.0(或者其他任何数),此时实际指数为0,所以机器精度是(2^{-23} imes 2^{0} over 1.0)。
三、C++程序实现
利用库求值
标准库<limits>中的numeric_limits类中包含了许多算数特殊值:
- 上溢值: std::numeric_limits<float>::max();
- 规约下溢值: std::numeric_limits<float>::min();
- 非规约下溢值: std::numeric_limits<float>::denorm_min();
- 机器精度: std::numeric_limits<float>::epsilon();
其中numeric_limits<float>中float可以换成int,double等其它类型。
实际二进制存储值
std::string get_binary(float f) { int index_byte, index_bit; unsigned int byte = 0; char ch, *p; std::string bin_f = ""; p = (char *)(&f); for (index_byte = sizeof(float)-1; index_byte>=0; index_byte--) { ch = *(p+index_byte); //从最高位开始取 byte = ch; //将地址中8个二进制位赋值成十进制数 for (index_bit = 1; index_bit<=8; index_bit++) { if (byte >=128) bin_f += "1"; else bin_f += "0"; //判断首位是1还是0 byte <<= 1; //将当前位变成首位 byte &= 255; //确保始终8个二进制位 } } return bin_f; }