数据在Bit的表示和计算
信息表达
进制转换
十进制转二进制
如何把十进制 142 转成二进制? 如下图:
不断将 142 除以 2 , 获取余数。除以 2 的次数越多,证明所得余数的位更高。
所以这里最后的 142 = 1 2 7 + 0 2 6 + 0 2 5 + 0 2 4 + 1 2 3 + 1 2 2 + 2 1 + 0 2 0
最后得出 14210 = 1000 11102。
这个不断除以2的过程就像将这个二进制不断右移,得到的余数就是每次右移截取的值。
十进制转十六进制
142 转换成十六进制的过程同理:
142 = 8 16 1 + 14 16 0 = 0x8e。
数据大小
C语言不同类型在 32 或 64 位系统的字节大小 :
字节顺序
0x1234567在大端法的表示 :
在小端法的表示 :
最高有效位在前面是大端法,在后面是小端法。
大多数兼容 Intel 机器都是用小端法。一些 Sun 和 Oracle 的服务器会用大端法。
布尔代数
异或相同的位为0
逻辑运算 !&& 、|| ,注意和~ 、& 、| 的区别
移位操作
移位操作分为左移、逻辑右移和算术右移。
逻辑右移,左边补0。 算数右移左边补符号位。
整数
整数取值范围
C语言整数在 64 位系统中不同的取值范围 :
32位系统long和unsigned long的取值范围会和int相同,其他的类型取值范围和64位一样。
可以通过limit.h获取各类型的取值范围:
SCHAR_MIN : 有符号位的char最小值
SCHAR_MAX : 有符号位的char最大值
CHAR_MIN : 无符号最小值
UCHAR_MAX : 无符号最大值
SHRT_MIN : 短整型最小值
SHRT_MAX : 短整型最大值
USHRT_MAX : 无符号短整型最大值
INT_MIN : 整形最小值
INT_MAX : 整形最大值
UINT_MAX : 无符号整形最大值
LONG_MIN : 长整型最小值
LONG_MAX : 长整型最大值
ULONG_MAX : 无符号长整形最大值
整数的编码
原码: 最高有效位为 1 时为负数。
反码: 因为原码的正数和负数相同不等于 0 ,所以让原码的负数除了符号位都取反。
补码: 补码是反码 + 1,是通常负数的表现形式。正数和补码负数相加会得到 0 (去掉超出有效位的1)。
补码1011表示为 -1 x 23 + 0 x 22 + 1 x 21 + 0 x 20 = -5
补码具有不对称性,TMin的负数还是TMin。因为正数比负数多一个0。
补码转无符号数
T2U16(-12,345) = -12,345 + 216 = 53,191
无符号转补码
扩展位
无符号扩展: 高位添加 0 即可。
补码扩展: 高位按符号位拓展。 如 1010 = -6, 拓展4位 1111 1010 = -6。
截断位
整体右移 位,左边补0。无符号值转换为mod 2 , 对 2 的取模。有符号值先转换成无符号值取模后进行U2T计算。
符号转换的注意事项
a. 在4位运算中5 + 13可以转换为5 - 3。对计算机来说13和-3的位是一样的,没有符号之分。
b. 给一个无符号参数传负值时会转换成一个很大的正整数。
c. 无符号数b > 无符号数a时, a - b的结果负数会转换成正数。
d. 有符号数和无符号数比较时(<、>、==)会转换成无符号数。
整数运算
加法
无符号加法:
补码加法:
求非(负)
补码求非(负):
补码求非的两种办法:
a. 取反加一。
b. 假设最右边的1位置是 , 把所有 的左边位置取反。
例如:
| 值 | 补码 | 非 | 补码 |
|---|---|---|---|
| 5 | 0101 | -5 | 1011 |
| 7 | 0111 | -7 | 1001 |
| -4 | 1100 | 4 | 0100 |
| 0 | 0000 | 0 | 0000 |
| 8 | 1000 | -8 | 1000 |
乘法
无符号乘法:
补码的乘法:
乘以常数
乘法指令需要10多个指令周期,而加法只需要1个指令周期。因此会把乘以常数转变为加减法和移位的组合。
移位组合有两种形式:
a. ( x << n ) + ( x << ( n - 1 ) ) + ( x << m )
b. ( x << ( n + 1 ) ) - ( x << m )
例子:
求 x 14
a. x ( 23 + 22 + 21 ) = ( x >> 3 ) + ( x >> 2 ) + ( x >> 1 )
b. x ( 24 - 21 ) = ( x >> 4 ) - ( x >> 1 )
补码乘法计算
如 -3 3:
两个都为负数如-3 -5:
截掉两个最高位,因为 n 位乘以 n 位,结果是 n + n 位,所以这里保留 8 位结果。左边还是要补符号位,特别的是最后一个乘积不是1101,而是1101的补码0011(双负数时)。
除法
对于除以 2 的幂,无符号数除法使用逻辑右移,补码使用算数右移。
补码的负数右移时,会向下舍入,而不是向零舍入:
比如这里k = 8 时,取整正确应该是 -48,但是这里返回是 -49。
要向0舍入,那么负数要添加一个偏移值(2k - 1)再进行移位。所以有以下表达式:
( x < 0 ? x + ( 1 << k ) - 1 : x ) >> k
除法无法像乘法一样,通过右移推广到任意的常数。
那如何计算如 26 5 ? 如下图:
整数的测试
问题: 由整数 x、 y 和无符号整数 ux ,以下式子是否成立 ?
a. x < 0 => (( x 2 ) < 0 )
b. ux >= 0
c. x & 7 == 7 => ( x << 30 ) < 0
d. ux > -1
e. x > y => - x < - y
f. x x >= 0
g. x > 0 && y > 0 => x + y > 0
h. x >= 0 => -x <= 0
i. x <= 0 => -x >= 0
j. ( x | -x ) >> 31 == -1
解:
a. 错, x = TMin 时 2x = 0。
b. 对
c. 对, 11000..., 是个负数
d. 错, -1为111..., 用不为真
e. 错, y为TMin时, -y也为TMin
f. 错, 正溢出为负
g. 错, 溢出为 0 或负数
h. 对
i. 错,TMin的负还是TMin
j, 错,为 0 ,010 为 0
浮点数
二进制小数
定点法表现形式:
这种形式通常用来表达很大的数,或者接近 0 的数比较有效。
例子:
101.11 = 1 22 + 1 21 + 1 20 + 1 2-1 + 1 2-2 = 5
所以有一些浮点数无法精确表达,如 、、。只能表达形如 x 2y形式的数。
这些数在有限位的形式下近似替代,如 来近似替代 。
IEEE浮点数
定点法无法有效很好表达非常大的数,如5 * 2100, 定点法要在 101 后面加 100 位 0。
IEEE浮点数表现形式:
V = ( -1 )s M 2E
s 表示符号位
M 表示小数位,单精度是0 ~ 22位(一共23位),64位是0 ~ 51位(一共52位)
E 表示阶码, 用于对浮点数加权,权重是2的E幂(E可能是负数)。e 在单精度是23 ~ 30位(一共8位),在双精度是52 ~ 62位(一共11位)。
浮点数数值的三种分类,以单精度为例:
Bias单精度为 127,双精度为 1023。
例子:
如何表示 float F = 15213.0 ?
- 将十进制转换成二进制
15213 10 = 11 1011 0110 1101 2 = 1.1 1011 0110 1101 2 * 213
根据公式 V = ( -1 )s x M x 2E
-
小数部分有
M = 1.1 1011 0110 1101
f = 110 1101 1011 0100 0000 0000 -
阶码部分有
E = 13
单精度Bias = 127, 所以 e = 140 = 1000 1100 2 -
所以浮点表示的值为:
浮点数的舍入
浮点数默认的舍入规则是四舍六入五向偶数舍入 ( 0也偶数 )。
例如:
1.2349999 舍入到 1.23
1.2350001 舍入到 1.24
1.2350000 舍入到 1.24
1.2450000 舍入到 1.24
这个舍入方式也能运用到二进制数中。
2 = 10. 00011 向下舍入到 10.00
2 = 10. 00110 向上舍入到 10.01
2 = 10. 11110 向上舍入到 11.00
2 = 10. 10100 向偶舍入到 10.10
XX.YY100 2 假如最有的 Y 是要舍入的位置。100 2 是中间值向偶舍入,大于这个值会向上舍入,小于这个值会向下舍入。
加法和乘法与阿贝尔群的区别
加法
- 满足交换律。 如 a + b = b + a
- 不满足结合律, a + ( b + c ) != ( a + b ) + c。 如 ( 3.14 + 1e10 ) - 1e10 = 0, 3.14 + ( 1e10 - 1e10 ) = 3.14。
- 几乎满足逆元 a - a = 0, 除了 Infinities 和 NaN。
- a >= b, 无法推导出 a + c >= b + c。 假如 a 是个很大的数可能产生正溢出。
乘法
- 满足交换律。 a * b = b * a。
- 不满足结合律 ( 1e10 1e20) 1e-20 = Infinities, 1e20 ( 1e20 1e-20 ) = 1e20。
类型转换
- int(32位)转换成float不会溢出,但是会舍入。
- int 和 float 转换成 double时可以精准表示。
- double 转换成 float时,大的数可能溢出成 + 或 -, 还可能被舍入。
- float 和 double转换成 int 时,向 0 舍入。还可能值溢出由正数变为负数。
检测测试
问题: 有 int x 、 float f 、double d, 判断以下式子是否推导正确
a. x == ( int )( float ) x
b. x == ( int )( double ) x
c. f == ( float )( double ) f
d. d == ( double )( float ) d
e. f == - ( -f )
f. ==
g. d < 0.0 => ( (d * 2) < 0.0 )
h. d > f => -f > -d
i. d d >= 0.0
j. (d + f) - d >= 0.0
解:
a. 错,精度丢失
b. 对
c. 对
d. 错,精度丢失和溢出问题
e. 对
f. 错, 舍入位 0
g. 对, 即使溢出,正负也只是由符号位觉得
h. 对, 浮点数满足单调性
i. 对
j. 错,不满足结合律
二进制的奇淫巧计
参考资料
《Computer Systems A Programmer’s Perspective》 —— Randal E. Bryant、David R. O’Hallaron