注:我以前找过有关源代码、反码、补码的资料,是一家过时的酒馆的博客。 源代码、逆向代码和补充代码都有详细说明。 为了方便,此处转载,著作权归原作者所有。 更深入的分析,可以参考作者的原文。
正文大部分来自这里。 接下来我将为本人补充一小部分“多数溢出”问题。
机器数量和真值
机器的数量
数在计算机中的二进制表示,称为此数的机器数。 机器的数量有符号,由计算机用一个数量的最高有效位存储符号,正数为0,负数为1
例如,十进制数中的数3,计算机的字长为8位,转换为二进制数时为00000011。 -3表示10000011。 那么,这里的0000011和10000011是机器数量。
真值
第一位是符号位,因此机器数量的型号值不等于真数值。 例如,上面的符号10000011的最高有效位1表示负,其真值为-3而不是形式值131。 (10000011转换为十进制后就是131。 因此,为了进行区分,将与有符号位的装置数相对应的真数值称为装置数的真值。
例如: 0000 0001的真值=000001=1,10000001的真值=-000 0001=-1
原始代码
原代码是在符号位上加上真值的绝对值后的代码,即用第一个位表示符号,用剩馀的位表示值。例如,如果是8位二进制,则为:
[ 1]原=0000 0001
[-1]原=1000 0001
第一位是符号位。 因为第一位是符号位,所以8位二进制数的可取范围为:
[ 1111111,01111111 ],即:
[-127,127 ]
源代码是人脑最容易理解和计算的表示方式。
反码
反码显示方法是正数的反码本身; 的反码基于其原始代码,符号位不变,其余各位相反。
[ 1]=[00000001]原=[00000001]反
[-1]=[10000001]原=[11111110]反
如果反码表示负数,人脑就无法直观地看到该数值。 通常将其转换为原始代码进行计算。
补充代码
补数显示方法是正数的补数本身; 的补数,根据其原始代码,符号的位数不变,剩下的各位取反,最后1.(即根据反码1 ) ) )。
[ 1]=[00000001]原=[00000001]反=[00000001]补
[-1]=[10000001]原=[ 111111110 ]反=[11111111]补
对于负数,补数的表示方法人脑也无法直观地识别该数值。通常需要转换成原来的代码计算该数值。
为什么要用原码、反码、补码呢
在开始深入学习之前,我的学习建议首先是“死记硬背”上的源代码、反码和辅助码的表示方法和计算方法。
据了解,目前计算机上可以用3种编码方式表示1个数据。 关于正数,这是因为3种编码方式的结果都是相同的:
[ 1]=[00000001]原=[00000001]反=[00000001]补
所以,没有必要解释太多。 但是,关于负:
[-1]=[10000001]原=[ 111111110 ]反=[11111111]补
可见源代码与反码和补码完全不同.源代码才是人脑直接识别并用于计算显示的,为什么会有反码和补码呢?
首先,人脑可以知道第一位是符号位,所以计算时根据符号位,选择真值区域的加减。 ()真值的概念在本文的开头)。 但是在计算机中,加减乘数已经是最基础的运算,需要尽量简单地设计。 计算机分辨“符号位”显然使计算机的基础电路设计非常复杂。 因此,提出了符号位也参加运算的方法。根据运算法则减去正数,就会加上:1-1=1(1(-1 )=0这样的负数,所以机器可以不进行减法运算而只进行加法运算,计算机运算的设计更好
于是,人们开始寻找使符号位参与运算,只保留加法的方法。 首先看源代码。 要计算十进制数的表达式: 1-1=0
1-1=1(-1 )=[00000001]原([ 100000001 ] )原=[10000010]原=-2
如果用原代码表示,符号位也参加计算的话,显然对减法运算来说是不正确的结果。因此,在计算机内部没有使用原代码表示一个数。
为了解决减去二维码的问题,出现了二维码。 计算十进制数的公式:
1-1=0
1-1=1(-1 ) )。
=[0000 0001]原[1000 0001]原
=[0000 0001]反[1111 1110]反
=[1111 1111]反=[1000 0000]原
=-0
用反码计算减法,发现真值的部分是正确的.而且唯一的问题其实在于“0”这个特殊的数值.上面的0和-0是一样的,但是0没有任何符号。
意义的. 而且会有[0000 0000]原和[1000 0000]原两个编码表示0.于是补码的出现, 解决了0的符号以及两个编码的问题:
1-1 = 1 + (-1)
= [0000 0001]原 + [1000 0001]原
= [0000 0001]补 + [1111 1111]补
= [0000 0000]补=[0000 0000]原
这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128:
(-1) + (-127) = [1000 0001]原 + [1111 1111]原
= [1111 1111]补 + [1000 0001]补
= [1000 0000]补
-1-127的结果应该是-128, 在用补码运算的结果中, [1000 0000]补 就是-128. 但是注意因为实际上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示.(对-128的补码表示[1000 0000]补算出来的原码是[0000 0000]原, 这是不正确的)
使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].
因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-231, 231-1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值.
补码表示的溢出问题
以下是本人的补充的理解,不知道是否正确:
由于计算机中的数字用补码表示,例如8bit的byte类型的表示范围为:
[-128, 127]
0 = [0000 0000](补)
-128 = [1000 0000](补)
127 = [0111 1111](补)
当byte类型的变量超上限127时,如:
+128 = -(-128)= 127 + 1
= [1111 1111](补)+ [0000 0001](补)
= [1000 0000](补)
= -128
+129 = 127 + 2
= [1111 1111](补)+ [0000 0001](补)
= [1000 0001](补)
= [1111 1111](原)
= -127
当byte类型的变量超过下限-128时:
-129 = -128 - 1
= [1000 0000](补) - [0000 0001](补)
= [0111 1111](补)
= 127
-130 = -128 - 2
= [1000 0000](补) - [0000 0010](补)
= [0111 1110](补)
= 126
byte a = -128, b = (byte) 128, c = (byte) 129, d = (byte) 130;
byte e = (byte) -129, f = (byte) -130;
System.out.println(a == ((byte)-a)); // true
System.out.println(b); // -128
System.out.println(c); // -127
System.out.println(d); // -126
System.out.println(e); // 127
System.out.println(f); // 126
1
2
3
4
5
6
7
8
大数溢出问题
int类型在32位系统中占4个字节、32bit,补码表示的的数据范围为:
[10000000 00000000 00000000 00000000] ~ [01111111 11111111 11111111 11111111]
[−231,231−1][−231,231−1]
[-2147483648, 2147483647]
在java中表示为:
[Integer.MIN_VALUE, Integer.MAX_VALUE]
与byte类型的表示一样,由于负数比正数多表示了一个数字。对下限去相反数后的数值会超过上限值,溢出到下限,因此下限的相反数与下限相等;对上限去相反数的数值为负值,该负值比下限的负值大1,在可以表示的范围内,因此上限的相反数是上限直接取负值。
// 2147483647 [01111111 11111111 11111111 11111111]
System.out.println(Integer.MAX_VALUE);
// -2147483648 [10000000 00000000 00000000 00000000]
System.out.println(Integer.MIN_VALUE);
// -2147483647 正常
System.out.println(-Integer.MAX_VALUE);
// -2147483648 2147483648,超过上限,发生溢出
System.out.println(-Integer.MIN_VALUE);
// true,2147483648 发生溢出
// 对下限去相反数后的数值会超过上限值,溢出到下限,因此下限的相反数与下限相等
System.out.println((Integer.MIN_VALUE == -Integer.MIN_VALUE));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这个特点在进行大数溢出判断时会用到,例如JKD源码中字符串转化为int类型的函数,Integer.parseInt(String str)中,只能使用对上限取反Integer.MAX_VALUE当成负数累减,而不能对下限取反Integer.MIN_VALUE当成正数累加。
Integer.parseInt(String str)源码:
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}
// radix是进制表示
public static int parseInt(String s, int radix)
throws NumberFormatException
{
/*
* WARNING: This method may be invoked early during VM initialization
* before IntegerCache is initialized. Care must be taken to not use
* the valueOf method.
*/
if (s == null) {
throw new NumberFormatException("null");
}
if (radix < Character.MIN_RADIX) {
throw new NumberFormatException("radix " + radix +
" less than Character.MIN_RADIX");
}
if (radix > Character.MAX_RADIX) {
throw new NumberFormatException("radix " + radix +
" greater than Character.MAX_RADIX");
}
int result = 0;
boolean negative = false;
int i = 0, len = s.length();
int limit = -Integer.MAX_VALUE; // 对上限取反不会溢出
int multmin;
int digit;
if (len > 0) {
char firstChar = s.charAt(0);
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
negative = true;
limit = Integer.MIN_VALUE;
} else if (firstChar != '+')
throw NumberFormatException.forInputString(s);
if (len == 1) // Cannot have lone "+" or "-"
throw NumberFormatException.forInputString(s);
i++;
}
multmin = limit / radix;
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
digit = Character.digit(s.charAt(i++),radix);
if (digit < 0) {
throw NumberFormatException.forInputString(s);
}
// 除以10必须放在不等式右侧防止左侧溢出
// 如果满足条件,说明 result*radix不会发生溢出
if (result < multmin) {
throw NumberFormatException.forInputString(s);
}
result *= radix;
// digit必须放在不等式右侧防止左侧溢出,判断防止最终溢出
// 如果满足条件说明 result-digit不会发生溢出
if (result < limit + digit) {
throw NumberFormatException.forInputString(s);
}
// 当成负数累减
result -= digit;
}
} else {
throw NumberFormatException.forInputString(s);
}
return negative ? result : -result;
}