机器数和真值
机器数
数据在计算机内的表示的形式叫做机器数,一般是二进制计数值(也可以说是数据在计算机内的二进制表示形式叫做机器数),数的符号用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,具体原因看下篇。
移码的好处
移码好处是方便比较大小。
为什么要出现符号位?
因为计算机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(补码))
负数的补码怎么变回原码?
假设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 - 博客园
这篇文章在搜索引擎中是很靠前的,但是其思想是不错的,但是推导和结论是有问题的,所以可以着重看看评论的关于错误的讨论