当前位置 博文首页 > 外星喵的博客:计算机如何进行负数运算?原码、反码、补码详解
原本机器只能表示正数,八位二进制就是0~255,也就是[00000000 , 1111 1111]。
为了让计算机理解负数于是科学家就想出了个办法,
现在要表示负数,取最高位0表示正数、1表示负数,本来有8位表示一个数的大小,现在变成了7位,其中[0 ~ 127]不变,原本的[128 ~ 255]就被用来表示负数了。其中原来的128就是-128,原来的255就是-1了。比如255在16位下的低8位为[11111111],如果强转为byte类型就会变成-1[11111111];
一个数在计算机中的二进制表示形式, 叫做这个数的机器数。
有符号数和无符号数是针对符号出现的两种机器数表示方法。同一个二进制数,对有符号数和无符号数具有不同的含义。
有符号数如: char, short ,int, long等类型的变量
无符号数(c语言下)如:unsigned char, unsigned short , unsigned int, unsigned long, 指针等类型的变量
计算机中的有符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同 。在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。
原码是在数值二进制表示下从左到右第一位,即最高位为符号位:正数该位为0,负数该位为1(注意原来的0有两种表示:+0和-0),其余位表示数值的大小。
反码表示法规定:正数的反码与其原码相同;负数的反码是对正数逐位取反,符号位保持为1。
换句话就是说:反码是对原码除了符号位以外所有位分别对1进行异或(^)操作得到的结果。
补码就是在反码的基础上加1得到的结果。
如何看了以上定义还不懂,那我们以32位的整数1和-1举例:
给定一个int整数i,如果i为负数,那么i的反码的值就是(i ^ Integer.MAX_VALUE),补码的值就是(i ^ Integer.MAX_VALUE)+1,若i为正数,则反码和补码同原码 。
其中Integer.MAX_VALUE = 2147483647,其原码:[ 01111111 11111111 11111111 11111111 ]
说白了,反码和补码的就是为负数而设定的,而反码只是补码的一个过渡产物,并无什么实际意义,我们需要记住的就是负数的原码和补码的关系和推导过程,因为计算机中负数都是用补码表示,所以我们要记得就是负数的补码如何推导出原码。
因为int太长太繁琐,下面我们以byte类型(8位)来举例:
1
原码:[ 00000001 ]
? ? (不变)
反码:[ 00000001 ]
? ? (不变)
补码:[ 00000010 ]
-1
原码:[ 10000001 ]
? (除符号位外逐位取反)
反码:[ 11111110 ]
? ? (+1)
补码:[ 11111111 ]
127
原码:[ 01111111 ]
? ?
反码:[ 01111111 ]
? ?
补码:[ 01111111 ]
因为原码第一位是符号位,所以byte类型的8位二进制数的取值范围就是:
即
[-127 , 127]
(这时可能有的同学会说博主你个水货,明明byte整形取值范围是-128~127,别急,请继续往下看)
-1
补码:[ 11111111 ]
? (除符号位外逐位取反)
反码:[ 1000000 ]
? ? (+1)
原码:[ 10000001 ]
二进制数中,两数的补码之和等于两数和的补码.
负数的反码=原码除符号位外其它位的数值取反,即“0”变“1”,“1”变“0”.
负数的补码=反码+1
负数的原码=负数补码的反码+1
任何正数的原码、反码和补码完全相同(即都是自身不变)
在计算机中,有符号的数都是采用补码来表示的.
计算的时候,符号位也可以参与运算.
1.使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理。
2.使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127]。
举个例子
(-1) + (-127) = 原码:[1000 0001] + 原码:[1111 1111]
? ????????????????? = 补码: [1111 1111] + 补码:[1000 0001]
? ????????????????? = 补码: [1000 0000]
? ????????????????? = -128
-1-127的结果就是-128, 有符号的数都是采用补码来表示的, 所以补码:[1000 0000] 就是-128.。
但是注意[1000 0000]实际上是使用以前的-0的补码来表示-128, 所以现在-0是被当作0来处理了(虽然有点绕,你只要记得-0被丢弃就行了)。
对于-128在有符号的8位进制下是得不到补码的,因为按照约定最高位是符号位,而它的低7位都是0,
所以-128其实是数溢出了,对于补码表示[1000 0000]逆推得到的原码是[0000 0000], 这又是不正确的。
假如-128是32位整数,那么他此时的对应的补码就是[11111111 11111111 11111111 10000000]。
其中:
n位bit位的整数,所能表示的范围是[-2^(n-1), 2^(n-1)-1]
一旦数字超过了这个限定,就会发生溢出,超过上限,叫做上溢出。超过下限,叫做下溢出。上溢出之后,又从下限开始:最大的数加1,就变成了最小的数。下溢出之后又从上限开始,最小的数减去1就变成了最大的数,如此循环往复。
如果一个N位数经过运算得到了一个N+1位的数,那么此时他的最高位就是溢出状态,是直接被舍弃的,举个例子:
一个无符号数255在byte(8位)类型中的二进制形式为255[ 11111111 ],如果此时对它进行加1操作,则会变成0[ 00000000 ],此时就发生了数的溢出,而如果它是一个short(16位)类型,其二进制形式便是255[ 00000000 11111111],加1后的结果为256[ 00000001 00000000],便没有发生溢出。
定义三个变量分别为a、b、m.
我们取a=-1 ,m= 256;
假设 (a-b)/m = -1,可以得到 b = 255,所以我们可以说-1和255同余
我们再以 c = a + b举例,其中c为有符号的byte类型,显然c小于256,可以得到:
c = c ^ (256 - 1) = c mod 256
???= (-1 + 127)mod 256
???= (255 + 127) mod 256
???= (255 + 1 + 126) mod 256
???= (256 + 126) mod 256
???= (0 + 126) mod 256
???= 126 [ 01111110 ]
我们用a+b得到结果126,对于机器而言过程如下:
定义一个byte x = -1 ;
原码:[ 10000001 ]
反码: [ 11111110 ] = 254 = 256 -1 - |x|
补码: [ 11111111 ] = 255 = 256 - |x|
对于byte类型x;它的补码就是2^8 - x的绝对值
(-1) + 127 = 原码:[ 1000 0001 ] + 原码:[ 1111 1111 ]
? ?????????????? = 原码:[ 10000000 ]
? ?????????????? = -128
(-1) + 127 = 原码:[ 1000 0001 ] + 原码:[ 0111 1111 ]
? ?????????????? = 补码:[ 1111 1111 ] + 补码: [ 0111 1111 ]
? ?????????????? = 255 + 127 (还是那句话计算机不理解负数,对它而言-1的补码其实就是255的原码,这也是为什么byte类型下127+1结果显示-128的原因)
? ?????????????? = 126
对,你还是没看错,255+127对于机器得到的结果就是126,原理就是前面讲过的数的溢出问题。
我们把byte类型的取值区间想象成一个圆环,它的周长是256,我们把它分为256份也就是256个点,每相邻两个点间距为1,如下图:
所以127 + 255 相到于从127这个点顺时针走了255个点,最后正好落在了126这个点位上,同理127 - 1 就是127这个点逆时针走了一个点,也是落在了126上。
(-1) + 127 则相当于从-1这个点顺时针出发经过127个点到了126上。
题外话:古人对南辕北辙有误解,因为地球是圆的,所以,只要一直往南,那么是能到北的。
(-1) + 127 = ((-1) + 127)mod 256
? ?????????????? =([ 11111111 ] + 127 )mod 256
? ?????????????? =(255 + 127 )mod 256
? ?????????????? = ?? 126
我们站在计算机的角度看它如何理解这一个过程: