机器数、真值、原码、反码、补码、移码

268 阅读6分钟

 机器数和真值

机器数

数据在计算机内的表示的形式叫做机器数,一般是二进制计数值(也可以说是数据在计算机内的二进制表示形式叫做机器数),数的符号用0、1表示(也就是正负表示),小数点隐含表示,不占据位数。

真值

带符号位的机器数对应的真正数值称为机器数的真值(由于首位为符号位,所以机器数的形式值就不等于真正的数值)

例如:10000011,其最高位1代表负,其真正数值是 -3 而不是形式值131

原码、反码、补码的概念以及计算方式

为了便于运算,带符号的机器数可采用原码、反码、补码、移码等编码方法。

以下均采用8位二进制做解释,也就是假设机器字长为8。

原码

数值的原码是符号位加表示真值的绝对值。数值0的原码有两种:00000000(+0)和1000000(-0)。

例子:

+1 = 00000001和-1 = 10000001

+127 = 01111111和-127 = 11111111

+0.5 = 0.1000000和-0.5 = 1.1000000

反码

正数的反码与原码一致,保持不变。

负数的反码是在原码的基础上符号位不变,其他位取反。

数值0的原码有两种:00000000(+0)和11111111(-0)。

例子:

+1 = 00000001和-1 = 11111110

+127 = 01111111和-127 = 10000000

+0.5 = 0.1000000和-0.5 = 1.0111111

补码

正数的补码与反码和原码一致,保持不变。

负数的补码是在其反码的基础上,符号位不变,末位+1。

数值0的补码是唯一:00000000(+0和-0)

一个n位补码在其符号位为1,其他位都为0时,表示为整数-(2^n - 1),其符号位的1,此时即表示符号位也表示数值位,这是将0的表示唯一化和扩大一个表示位。

移码

是一种将全0码映射为最小负值、全1码映射为最大正值的编码方案,通常移码为真值+偏移量。对于n位二进制数,偏移量通常为2^(n - 1),比如8位二进制数,其范围为-128 ~ 127,那么偏移量为128(100000),使其移码范围为0 ~ 255,相当于将其符号位给取反,并将符号位作为数值位。

IEEE 754浮点数表示中移码是非标准的,它的偏移值为2^(n - 1)-1,具体原因看下篇。

移码的好处

移码好处是方便比较大小。

zh.m.wikipedia.org

为什么要出现符号位?

因为计算机CPU运算器只实现了加法器,没有实现减法器,所以计算机在进行减法运算是通过加一个负数来实现相同目的的(也就是将3 - 2转化为3 + (-2)),所以需要符号位来表示负数。

溢出

由于计算机位数的限制,所以整数的表示有上限和下限,在Java中,int型是32位,它的最大值也就是上限是2^31-1(最高位是符号位,所以是 2 的 31 次方而不是 32 次方),最小值也就是下限是-2^31,超过上限叫做上溢出,超过下限叫做下溢出。

上溢出:01111.....111111(n-1位的1, 2^(n - 1) - 1) + 1 = 1000...0000(n-1位的0, -2^(n - 1))。可以看出超过最大值上溢出之后就变成最小值,这是取模和余数的概念。

其取模的除数为数据的个数 = 上限 - 下限 + 1 = (2^(n - 1) - 1) - (-2^(n - 1)) + 1=2 x 2^(n-1) - 1 + 1= 2^n - 1 + 1。

为何不直接记录为2^n?

因为记录2^n需要n + 1位,超出了n位表示的范围,其n位最大表示值为2^n - 1,所以将2^n记为2^n - 1 + 1,比如2^32 = 1000....0000(32个0),所以将其记作:111111....1111(32个1)+ 1。

计算机的减法怎么转化为加法?

为何不使用原码直接计算?

1 - 1 = 1 + (-1) = 0000...0001 + 1000...0001 = 1000...0010 = -2

3 - 2 = 3 + (-2) = 0000...0011 + 1000...0010 = 1000...01001 = -5

-1 - 1 = -1 + (-1) = 1000...0001 + 1000...0001 = 0000...0010 = 2

根据上面这两个例子可以发现,负数的原码并不适用于将减法操作转化为加法。

反码、补码的由来

这部分是来自于极客时间的程序员的数学基础课的内容,但是有一些不够直白,所以将其表达的意思整理出来,如果有误请大家能够评论指出。

假设有i - j,其中j为正数。如果i - j加上取模的除数,那么会形成溢出,并正好能够获得我们想要的i - j 的运算结果。

注:2^n转化为2^n - 1 + 1,参考上文——为何不直接记录为2^n?
i - j =  (i - j) + 2^n = (i - j) + (2^n - 1 + 1) = i + (2^n - 1 - j + 1)

由于2^n - 1为n位1,减去j,相当于将j除了符号位之外按位取反(0 变 1,1 变 0),假设位数为32,那么2^n - 1 = 11111....1111(32位1),假设j=-5,那么其为100000..000101,2^n - 1 - j为

1111.....11111010,其相当于j除了符号位之外按位取反(0 变 1,1 变 0)。

于是将其定义为j的反码,那么i - j = i + (-j(反码)) + 1

然后将 (-j(反码)) + 1定义为j的补码,于是i - j = i + (-j(补码))

OverflowMechanism.png

负数的补码怎么变回原码?

假设32位机器:

设某负数X,则X+X(反)=X(补) + X(反)(补)= 0xFFFFFFFF,其原码值为-1,所以X+X(反)+1 = 0,由此可以得出0 - X = X(反)+ 1,根据补码的定义:X(反)+ 1,那么0 - X也是X的补码。

那么Y - X = Y + (0 - X) = Y + X(反)+ 1 = Y + X(补),也就是Y减去X等于Y加X的补码。

负数的补码的补码是原码

0 - X(补)= X(补)(反)+ 1 = X(补)(补) = X

推导过程如下:

由X(补) + X(补)(反)+ 1 = 0得出X(补) = 0 - (X(补)(反)+ 1)

由X+X(反)+1 = 0得出X = 0 - (X(反)+ 1) = 0 - X(补)= 0 - (0 - (X(补)(反)+ 1)) = X(补)(反)+ 1 = X(补)(补)

或者由X(补) + X(补)(反)+ 1 = 0 => 0 - X(补)= X(补)(反)+ 1,再根据X(补) = 0 - X,推导出0 - (0 - X) = X(补)(反)+ 1 => X = X(补)(反)+ 1 = X(补)(补)

若是哪里有理解错误的或写错的地方,望各位读者评论或者私信指正,我会及时更正,不胜感激。

原码, 反码, 补码 详解 - ziqiu.zhang - 博客园

这篇文章在搜索引擎中是很靠前的,但是其思想是不错的,但是推导和结论是有问题的,所以可以着重看看评论的关于错误的讨论