文章目录
一、引例
- 看下下面这段代码,会输出什么结果呢?
double x = 0;
for (int i = 0; i < 10; ++i) {
x += 0.1;
}
printf("%d\n", x == 1);
- 输出如下:
0
- 引起这种反差的原因就是浮点误差,浮点数在存储的时候是有精度误差的,所以进行浮点数等于判定的时候不能用 ‘==’,那么接下来我们看下浮点数的表示;
二、浮点数表示
1、IEEE 754
- IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。这个标准定义了表示浮点数的格式(包括负零 -0)与反常值),一些特殊数值(无穷(Inf)与非数值(NaN)),以及这些数值的“浮点数运算符”。
- 基于这个规定,任何浮点数都可以表示成如下形式:
( V a l u e ) 2 = S i g n ∗ F r a c t i o n ∗ E x p o n e n t (Value)_2 = Sign * Fraction * {Exponent} (Value)2=Sign∗Fraction∗Exponent
Sign 表示符号位,代表正数还是负数;
Faction 代表尾数,用科学计数法表示,一定是以1开头,例如: 1.010100111;
Exponent 代表指数,1001 实际上是 2 9 2^9 29 的意思;
2、单精度和双精度
- C++ 中两种浮点数, f l o a t float float 和 d o u b l e double double,分别代表单精度和双精度;
- 单精度尾数占23个比特位,指数占8个比特位;双精度尾数占52个比特位,指数占11个比特位;
- 由于结构相同,这里只介绍单精度浮点数;
3、单精度浮点数表示法
- 如图为 32位 的浮点数的二进制表示,总共 32 个比特位(取值 0 或 1);
- 1)S 在最高位,代表符号,负数为1,正数为0;
- 2)E 为从高到低的第 30 - 23 位,总共 8 位,代表指数偏移,存储的是加上了 127 以后的值(原因是指数有可能是负数);
- 3)F 代表二进制科学计数法的尾数,总共 23 位,因为是科学计数法,所以开头一定是 “1.” 开头,所以尾数有效位数为 24 位;
4、举例说明
【示例】 对于 − 18.375 -18.375 −18.375 这个浮点数,求出它的二进制存储格式;
- 1)首先去掉符号,最后在二进制表示的最高位置1就行,那么接下来就是求 18.375 的实际二进制表示;
- 2)整数部分 18 的二进制为: 18 = 16 + 2 = 2 4 + 2 1 = ( 10010 ) 2 18 = 16 + 2 = 2^4 + 2^1 = (10010)_2 18=16+2=24+21=(10010)2
- 3)小数部分 0.375 的二进制表示为: 0.375 = 0.125 + 0.25 = 2 − 3 + 2 − 2 = ( . 011 ) 2 0.375 = 0.125 + 0.25 = 2^{-3} + 2^{-2} = (.011)_2 0.375=0.125+0.25=2−3+2−2=(.011)2
- 4)整合整数和小数部分后,得到: ( 18.375 ) 10 = ( 10010.011 ) 2 (18.375)_{10} = (10010.011)_2 (18.375)10=(10010.011)2
- 5)表示成科学计数法得到: ( 18.375 ) 10 = ( 1.0010011 ) 2 ∗ ( 100 ) 2 (18.375)_{10} = (1.0010011)_2 * (100)_2 (18.375)10=(1.0010011)2∗(100)2
- 6)于是得到尾数 F = 0010011 F = 0010011 F=0010011(舍弃掉科学计数法中的 “1.”),后面用 0 补齐;阶数需要加上127,, E = ( 100 + 1111111 ) 2 = ( 10000011 ) 2 E = (100 + 1111111)_2 = (10000011)_2 E=(100+1111111)2=(10000011)2;符号位 S = 1 S = 1 S=1;
S: 1
E: 10000011
F: 00100110000000000000000
- 7)填充到二进制中得到:
SEF = 11000001 10010011 00000000 00000000
5、代码测试
- C/C++ 中可以用如下代码对浮点数取地址转换成整型后输出整数的二进制表示;
float a = -18.375;
unsigned int v = *((unsigned int *)&a);
三、浮点数判定
1、精度定义
- 在 C++ 中, 1 e − 6 1e-6 1e−6 代表 1 0 − 6 10^{-6} 10−6,即 0.000001 0.000001 0.000001,是一个比较合适的精度值;
#define eps 1e-6
2、相等判定
- 介于浮点数的表示方式,不能用 ‘==’ 进行相等判定,必须将两数相减,取绝对值以后,根据结果是否小于某个精度来判定两者是否相等;
bool EQ(double a, double b) { // EQual
return fabs(a - b) < eps;
}
3、不相等判定
- ‘不相等’ 就是 ‘相等’ 的 ‘非’(取反);
bool NEQ(double a, double b) { // NotEQual
return !EQ(a, b);
}
4、大于等于判定
- ‘大于等于’ 就是 ‘大于 或 等于’ 的意思,需要拆分成如下形式:
bool GET(double a, double b) { // GreaterEqualThan
return a > b || EQ(a, b);
}
5、小于等于判定
- ‘小于等于’ 就是 ‘小于 或 等于’ 的意思,需要拆分成如下形式:
bool SET(double a, double b) { // SmallerEqualThan
return a < b || EQ(a, b);
}
6、小于判定
- ‘小于’ 就是 ‘大于等于’ 的 ‘非’,需要拆分成如下形式:
- 注意:千万不能直接用 a < b a < b a<b;
bool ST(double a, double b) { // SmallerThan
return a < b && NEQ(a, b);
}
7、大于判定
- ‘大于’ 就是 ‘小于等于’ 的 ‘非’,需要拆分成如下形式:
- 注意:千万不能直接用 a > b a > b a>b;
bool GT(double a, double b) { // GreaterThan
return a > b && NEQ(a, b);
}