数据在Bit的表示和计算

272 阅读8分钟

数据在Bit的表示和计算

信息表达

进制转换

十进制转二进制

如何把十进制 142 转成二进制? 如下图:

1670601112123.png

不断将 142 除以 2 , 获取余数。除以 2 的次数越多,证明所得余数的位更高。

所以这里最后的 142 = 1 ×\times 2 7 + 0 ×\times 2 6 + 0 ×\times 2 5 + 0 ×\times 2 4 + 1 ×\times 2 3 + 1 ×\times 2 2 + ×\times 2 1 + 0 ×\times 2 0

最后得出 14210 = 1000 11102

这个不断除以2的过程就像将这个二进制不断右移,得到的余数就是每次右移截取的值。

十进制转十六进制

142 转换成十六进制的过程同理:

image.png

142 = 8 ×\times 16 1 + 14 ×\times 16 0 = 0x8e。

数据大小

C语言不同类型在 32 或 64 位系统的字节大小 :

image.png

字节顺序

0x1234567在大端法的表示 :

image.png

在小端法的表示 :

image.png

最高有效位在前面是大端法,在后面是小端法。
大多数兼容 Intel 机器都是用小端法。一些 Sun 和 Oracle 的服务器会用大端法。

布尔代数

异或相同的位为0

image.png

逻辑运算 !&& 、|| ,注意和~ 、& 、| 的区别

image.png

移位操作

移位操作分为左移、逻辑右移和算术右移。

image.png

逻辑右移,左边补0。 算数右移左边补符号位。

整数

整数取值范围

C语言整数在 64 位系统中不同的取值范围 :

image.png

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 : 无符号长整形最大值

整数的编码

1670469292083.png

原码: 最高有效位为 1 时为负数。
反码: 因为原码的正数和负数相同不等于 0 ,所以让原码的负数除了符号位都取反。
补码: 补码是反码 + 1,是通常负数的表现形式。正数和补码负数相加会得到 0 (去掉超出有效位的1)。

补码1011表示为 -1 x 23 + 0 x 22 + 1 x 21 + 0 x 20 = -5

补码具有不对称性,TMinω\omega的负数还是TMinω\omega。因为正数比负数多一个0。

补码转无符号数

image.png

T2U16(-12,345) = -12,345 + 216 = 53,191

无符号转补码

image.png

扩展位

无符号扩展: 高位添加 0 即可。
补码扩展: 高位按符号位拓展。 如 1010 = -6, 拓展4位 1111 1010 = -6。

截断位

整体右移 κ\kappa 位,左边补0。无符号值转换为mod 2 κ\kappa, 对 2 κ\kappa 的取模。有符号值先转换成无符号值取模后进行U2Tκ\kappa计算。

符号转换的注意事项

a. 在4位运算中5 + 13可以转换为5 - 3。对计算机来说13和-3的位是一样的,没有符号之分。
b. 给一个无符号参数传负值时会转换成一个很大的正整数。
c. 无符号数b > 无符号数a时, a - b的结果负数会转换成正数。
d. 有符号数和无符号数比较时(<、>、==)会转换成无符号数。

整数运算

加法

无符号加法:

image.png

补码加法:

image.png

求非(负)

补码求非(负):

image.png

补码求非的两种办法:
a. 取反加一
b. 假设最右边的1位置是 κ\kappa, 把所有κ\kappa 的左边位置取反

例如:

补码补码
50101-51011
70111-71001
-4110040100
0000000000
81000-81000

乘法

无符号乘法:

image.png

补码的乘法:

image.png

乘以常数

乘法指令需要10多个指令周期,而加法只需要1个指令周期。因此会把乘以常数转变为加减法和移位的组合。

移位组合有两种形式:
a. ( x << n ) + ( x << ( n - 1 ) ) + ( x << m )
b. ( x << ( n + 1 ) ) - ( x << m )

例子:
求 x ×\times 14
a. x ×\times ( 23 + 22 + 21 ) = ( x >> 3 ) + ( x >> 2 ) + ( x >> 1 )
b. x ×\times ( 24 - 21 ) = ( x >> 4 ) - ( x >> 1 )

补码乘法计算

1670603156492.png

如 -3 ×\times 3:

1670603767225.png

如红色标记左边(高位)补符号位。

两个都为负数如-3 ×\times -5:

image.png

截掉两个最高位,因为 n 位乘以 n 位,结果是 n + n 位,所以这里保留 8 位结果。左边还是要补符号位,特别的是最后一个乘积不是1101,而是1101的补码0011(双负数时)。

除法

对于除以 2 的幂,无符号数除法使用逻辑右移,补码使用算数右移

补码的负数右移时,会向下舍入,而不是向零舍入:

image.png

比如这里k = 8 时,取整正确应该是 -48,但是这里返回是 -49。

要向0舍入,那么负数要添加一个偏移值(2k - 1)再进行移位。所以有以下表达式:
( x < 0 ? x + ( 1 << k ) - 1 : x ) >> k

除法无法像乘法一样,通过右移推广到任意的常数。

那如何计算如 26 ÷\div 5 ? 如下图:

1670598731507.png

整数的测试

问题: 由整数 x、 y 和无符号整数 ux ,以下式子是否成立 ?
a.  x < 0  =>  (( x ×\times 2 ) < 0 )
b.  ux >= 0
c.  x & 7 == 7  =>  ( x << 30 ) < 0
d.  ux > -1
e.  x > y  =>  - x < - y
f.  x ×\times 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

浮点数

二进制小数

定点法表现形式:

image.png

这种形式通常用来表达很大的数,或者接近 0 的数比较有效。

例子:

101.11 = 1 ×\times 22 + 1 ×\times 21 + 1 ×\times 20 + 1 ×\times 2-1 + 1 ×\times 2-2 = 534{3 \over 4}

所以有一些浮点数无法精确表达,如13{1 \over 3}15{1 \over 5}17{1 \over 7}。只能表达形如 x ×\times 2y形式的数。

这些数在有限位的形式下近似替代,如 51265{51 \over 265} 来近似替代 15{1 \over 5}

IEEE浮点数

定点法无法有效很好表达非常大的数,如5 * 2100, 定点法要在 101 后面加 100 位 0。

IEEE浮点数表现形式:
V = ( -1 )s ×\times M ×\times 2E
s 表示符号位
M 表示小数位,单精度是0 ~ 22位(一共23位),64位是0 ~ 51位(一共52位)
E 表示阶码, 用于对浮点数加权,权重是2的E幂(E可能是负数)。e 在单精度是23 ~ 30位(一共8位),在双精度是52 ~ 62位(一共11位)。

image.png

浮点数数值的三种分类,以单精度为例:

1670567514235.png

Bias单精度为 127,双精度为 1023。

例子:
如何表示 float F = 15213.0 ?

  1. 将十进制转换成二进制
    15213 10 = 11 1011 0110 1101 2 = 1.1 1011 0110 1101 2 * 213

  根据公式 V = ( -1 )s x M x 2E

  1. 小数部分有
    M = 1.1 1011 0110 1101
    f = 110 1101 1011 0100 0000 0000

  2. 阶码部分有
    E = 13
    单精度Bias = 127, 所以 e = 140 = 1000 1100 2

  3. 所以浮点表示的值为:
    image.png

浮点数的舍入

浮点数默认的舍入规则是四舍六入五向偶数舍入 ( 0也偶数 )

例如:
1.2349999 舍入到 1.23
1.2350001 舍入到 1.24
1.2350000 舍入到 1.24
1.2450000 舍入到 1.24

这个舍入方式也能运用到二进制数中。
2 232{2 \over 32} = 10. 00011 向下舍入到 10.00
2 316{3 \over 16} = 10. 00110 向上舍入到 10.01
2 78{7 \over 8} = 10. 11110 向上舍入到 11.00
2 58{5 \over 8} = 10. 10100 向偶舍入到 10.10

XX.YY100 2 假如最有的 Y 是要舍入的位置。100 2 是中间值向偶舍入,大于这个值会向上舍入,小于这个值会向下舍入。

加法和乘法与阿贝尔群的区别

加法

  1. 满足交换律。 如 a + b = b + a
  2. 不满足结合律, a + ( b + c ) != ( a + b ) + c。 如 ( 3.14 + 1e10 ) - 1e10 = 0, 3.14 + ( 1e10 - 1e10 ) = 3.14。
  3. 几乎满足逆元 a - a = 0, 除了 Infinities 和 NaN。
  4. a >= b, 无法推导出 a + c >= b + c。 假如 a 是个很大的数可能产生正溢出。

乘法

  1. 满足交换律。 a * b = b * a。
  2. 不满足结合律 ( 1e10 ×\times 1e20)×\times 1e-20 = Infinities, 1e20 ×\times ( 1e20 ×\times 1e-20 ) = 1e20。

类型转换

  1. int(32位)转换成float不会溢出,但是会舍入。
  2. int 和 float 转换成 double时可以精准表示。
  3. double 转换成 float时,大的数可能溢出成 +\infty 或 -\infty, 还可能被舍入。
  4. 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.  23{2 \over 3} == 23.0{2 \over 3.0}
g.  d < 0.0  =>  ( (d * 2) < 0.0 )
h.  d > f  =>  -f > -d
i.  d ×\times d >= 0.0
j.  (d + f) - d >= 0.0

解:
a. 错,精度丢失
b. 对
c. 对
d. 错,精度丢失和溢出问题
e. 对
f. 错, 23{2 \over 3} 舍入位 0
g. 对, 即使溢出,正负也只是由符号位觉得
h. 对, 浮点数满足单调性
i. 对 j. 错,不满足结合律

二进制的奇淫巧计

参考资料

《Computer Systems A Programmer’s Perspective》 —— Randal E. Bryant、David R. O’Hallaron