一 整数
首先我们来讲一讲背景,整数在不同的平台下可能会有不同的大小,一般来说都是4个字节,也就是32位,但是在一些老的平台上可能会占用两个字节,也就是16位。这里指定整数就是32位的整数,然后对32位的整数的表示范围进行分析。
1. 无符号整数
整数要分正数和负数,但是对于一个无符号整数来说,他只有正,标准中是用第一位标志正负,0代表正,1代表负。因为无符号整数不用一位去标识,所以对于无符号整数来说,所有位数为都为1即为最大,都为0为最小。
对于一个位数为w的二进制数:
那么无符号整数计算方式为:
下面测验一下最大的无符号整数:
int main(void) {
//cout << sizeof(unsigned int) << endl;
unsigned int unMax = 0b11111111111111111111111111111111;
cout << "位都为1的无符号整数为:" << unMax << endl;
cout <<"最大无符号整数为:" << UINT32_MAX << endl;
return 0;
}
结果:
位都为1的无符号整数为:4294967295
最大无符号整数为:4294967295
0b表示二进制。
2. 有符号整数
与一般人的直觉不同,有符号整数的表示并不是单独用一位来表示正负,而是采用补码的方式最大限度的利用了所有的位数。
对于一个位数为w的二进制数: 补码计算方式为:
根据等比数列求和公式:补码最后一项的最大和 (所有均为1时) 为: 所以在第一位为1时,就决定了这一定是一个负数,相反的,在第一位为0时,说明一定为正数,那么其表示范围为多少?做实验:
int main(void) {
int intMax = 0b01111111111111111111111111111111;
int intMin = 0b10000000000000000000000000000000;
cout << "首位为0其余都为1的32位整数为:" << intMax << endl;
cout <<"最大整数为:" << INT_MAX << endl;
cout << "首位为1其余都为0的32位整数为:" << intMin << endl;
cout <<"最大整数为:" << INT_MIN << endl;
return 0;
}
结果:
首位为0其余都为1的32位整数为:2147483647
最大整数为:2147483647
首位为1其余都为0的32位整数为:-2147483648
最大整数为:-2147483648
为什么要用补码的方式而不是像无符号整数那样计算,然后单独用一位表示正负呢?
如果我们用一位来表示正负,其他的按照无符号整数那么计算,那么0就会出现两次,浪费了一个数字的表示空间。
3 表示范围
正如之前计算的一样:
- 最大无符号整数 = 11111111111111111111111111111111(二进制) = 4294967295 (十进制)
- 最小无符号整数 = 0000000000000000000000000000(二进制) = 0 (十进制)
- 最大正整数 = 01111111111111111111111111111111(二进制) = 2147483647(十进制)
- 最小负整数 = 10000000000000000000000000000000(二进制) = -2147483648(十进制)
- 全为1的有符号整数 = 11111111111111111111111111111111(二进制) = -1(十进制)
- 全为0的有符号整数 = 00000000000000000000000000000000(二进制) = 0(十进制)
二 浮点数
1 标准浮点数形式
- 符号位:浮点数有单独的一位来表示正负:第1位为零的时候为正,第1位为一的时候为负。
- 尾数值:表示小数点后面的值,比如
-
阶码值:就是2的多少次方。
以下内容的M, E, s皆遵从上述定义。
2 单,双精度浮点数表示
名称 | 单精度长度 | 双精度长度 |
---|---|---|
符号(sign) | 1 | 1 |
尾数(frac) | 23 | 52 |
阶码(exp) | 8 | 11 |
也就是说对于一个32位的浮点数float来说,他有一位是符号位,23位是尾数位,8位为阶码位。对于一个64位的double浮点数来说他有一位是符号位,52位是尾数位,11位阶码位。
可以看到,尾数位越多,那么这个浮点数能表示的数字就越精确,如果阶码位越多,那么浮点数能表示的范围就越大,但是同样的,它能表示的这个不连续区间的间隔就越大,说白了计算机是用一系列固定数量的离散点描述浮点数,离散点个数是确定的,如果想表示更大的浮点数,就得接受离散点之间的相对举例增大,也就是说浮点数不够精确。
3 单精度浮点数细节(标准情况)
下面以单精度浮点数为例,解释一下具体的浮点数计算,双精度浮点数和单精度浮点数大同小异。
先来说一说阶码,如果不用符号位或者补码的方式,那就是没有正负的,对于指数来说,负指数代表的是除法,只有这样才会出现浮点数,IEEE754标准人为的规定了一个偏移值,阶码的计算值减去这个偏移值才是真实的阶码值。
这里有一个小工具:IIEEE754计算器
阶码的实际值计算:
对于32位浮点数:,
阶码全为0和全为1是两种特殊情况,会走不同的逻辑:所以阶码本身的范围为:
阶码的实际表示范围为:,
尾数值:
假设尾数值的二进制转十进制值为,那么实际值,例如:
3 单精度浮点数细节(非规格化情况)
或许你注意到了,按照之前的表示方式,0是没有办法精确表示的,对于一个32位的正浮点数,最接近0的数为:
,问题很大。
所以指定了,当阶码位全为0时,走入非规格化逻辑:
- 阶码实际值为:
- 尾数实际值:
所以:
4 特殊情况
阶码位全为1时为特殊情况:
-
(1) 当小数位都为0时表示无穷,s为0表示正无穷,s为-1表示负无穷。
-
(2) 当小数位不全为0时表示溢出(NaN)。
5 一些特殊浮点数
- (1) 大于0的最小浮点数:
手动计算器算的值:
现成工具计算的值:
- (2) 标准规格下最接近于0的值:
- (3) 最大存在的浮点数:
- (4) 1的准确表示:
- (5) 负数与整数完全对称,就不赘述了。
6 总结
最后以一个小例子结尾:
如果以较高精度输出,f会是准确值吗?
int main(void) {
float f = 0.023 + 0.1;
cout << "f = " << setprecision(12) << f << endl;
return 0;
}
结果:
f = 0.123000003397
浮点数并不像整数那样可靠,所以在计算浮点数的时候,你的加减乘除的顺序可能就会得到不同的计算结果,所以对待浮点数一定要谨慎,在对浮点数精度要求比较高的情况下,可以考虑使用BigDecimal。