阅读目录

  • 原码、反码、补码
    • 机器数 和 真值
    • 原码、反码、补码的基础
    • Python中负数的处理
    • 负数的补码如何转成十进制
  • 位运算符 和 移位运算符
    • 基本概述
    • 妙用
  • 二进制涉及的算法

原码、反码、补码

机器数 和 真值

  • 机器数:
    一个数在计算机中的二进制表示形式, 叫做这个数的机器数。
    机器数是带符号的,在计算机用一个数的最高位,称为符号位:用来存放符号, 正数为0, 负数为1.
    例如:十进制中的数 +3 ,假设计算机字长为8位,转换成二进制就是:00000011 ;如果是 -3 ,就是 10000011;这里的 00000011 和 10000011 就是机器数(其实就是原码 表示形式)
  • 真值:
    因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数 10000011,其最高位1代表负,其真正数值是 -3 而不是 “无符号数” 131(二进制数:10000011 转换成十进制等于131)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值(符号位直接按照0或者1 转换成 “+”“-” 占一个位置);例如:0000 0001的真值 = +000 0001 = +1;1000 0001的真值 = –000 0001 = –1;所以 二进制表示形式:10000011 --> - 000 0011 它的真值为 -3 ;其二进制结果为 131

原码、反码、补码的基础

  • 对于一个数,计算机要使用一定的编码方式进行存储:原码,反码,补码是机器存储一个具体数字的编码方式;既然有三种存储形式,那么计算机会选取哪种通用形式呢?答案是:选取补码,即数值在计算机中是以补码的形式来存储的,下面分步来说明为什么要用补码来存?

  • 原码

(1)原码(true form)是一种计算机中对数字的二进制定点表示方法。原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数该位为0,负数该位为1(0有两种表示:+0和-0),其余位表示数值的大小!比如如果是8位二进制 1 :

[+1]原 = 0000 0001[-1]原 = 1000 0001

(2)因为第一位是符号位,所以8位二进制数的取值范围就是:

[1111 1111 , 0111 1111]
#换成 数字为:
[-127 , 127]

(3)原码的优缺点:优点是简单直观,大脑最容易理解,例如,我们用8位二进制表示一个数,+11的原码为00001011,-11的原码就是10001011;缺点就是:原码不能直接参加运算,如果运算可能会出错。例如:数学上,1+(-1)=0,而在二进制中:

00000001+10000001=10000010 # 换算成十进制为-2 ;这显然出错了

所以原码的符号位不能直接参与运算,必须和其他位分开,这就增加了硬件的开销和复杂性

  • 反码

正数的反码还是其本身负数的反码是在其原码的基础上,符号位不变,其余各个位取反

[+1] = [00000001]原 = [00000001]反[-1] = [10000001]原 = [11111110]反'''
如果一个反码表示的是负数, 除了直观的看到它的最高位是1,它表示是个负数外,
我们无法直观的得出来它的具体数值,通常要将其转换成原码再计算
这里假如直接将负数的二进制反码,按:最高位为符号位“-” 剩下的位数按照二进制来转换的话
'''
11111110 --> -126 显然也不是原来的值 -1
  • 补码

正数的补码还是其本身负数的补码是其反码+1(也即是:在其原码的基础上, 符号位不变, 其余各位取反, 最后+1)

[+1] = [00000001]原 = [00000001]反 = [00000001]补[-1] = [10000001]原 = [11111110]反 = [11111111]补# 如果一个补码表示的是负数, 除了直观的看到它的最高位是1,它表示是个负数外,
# 我们无法直观的得出来它的具体数值,通常要将其转换成原码再计算
# 这里假如直接将负数的二进制补码,按:最高位为符号位“-” 剩下的位数按照二进制来转换的话
11111111 --> -127 显然也不是原来的值 -1
  • 计算机为什么选用以补码的形式来存储?

(1)计算机只有加法运算,没有设置减法运算!原因是:对于计算机,加减乘数已经是最基础的运算,要设计的尽量简单;计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂!于是人们想出了将符号位也参与运算的方法;我们知道,根据运算法则:减去一个正数等于加上一个负数,即: 1-1 = 1 + (-1) = 0;所以机器可以只有加法而没有减法,这样计算机运算的设计就更简单!
那么计算机要选取哪种码值来做加法运算呢?我们先来一一尝试,并看结果为什么要选择补码!

# 先补充:计算机是如何计算 减法 的,看下面的例子(这里我们假设已经知道是选取补码来运算)
# 求:1-2 的值
# 首先:将1-2 变成 1+(-2),然后分别将1的补码和-2的补码拿来运算
0000 ... 0001 (正数 1补码还是本身)
+
1111 ... 1110 (-2 的补码)
=
1111 ... 1111 (补码)
# 上面结果一看,不对啊,怎么不是-1啊?不急,有规定,我们先看最高位符号位为1,说明它是个负数
# 如果得到的是负数的话,我们需要将它转换成原码的表示形式,如果是正数(最高位符号位为0)则不需要
# 所以我们接着做:
1111 ... 1111 (补码)
1111 ... 1110 (反码)
1000 ... 0001 (原码)
即结果为-1 

(2)如果选择 原码来 存储:

# 假设要计算十进制的表达式: 1-1=0
1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2 '''
所以如果用原码表示, 让符号位也参与计算, 显然对于减法来说, 结果是不正确的,
这也就是为何计算机内部不使用原码表示一个数
'''

(3)如果选择 用 反码 来存储:

# 同样假设要计算十进制的表达式: 1-1=0
1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反
# 再将 反码 转成 原码:
[1111 1111]反 = [1000 0000]原 = -0 '''
看上去可行?因为按照我们通常认为的 0 和 -0 是一样;如果 0 和 -0 是一样的话,那么就有 [0000 0000]原和 [1000 0000]原 两个编码表示0,这样显然会造成混乱,而且[1000 0000]也用来表示0造成浪费!
'''

(4)看了上面两种码值的缺点后,我们选取补码的话,就能很好的解决0的符号以及两个编码的问题:

# 同样假设要计算十进制的表达式: 1-1=0 这次我们选取补码
1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补
(这里或许会有一个疑问:[0000 0001]补 + [1111 1111]补 相加 [0000 0000]补 进位后,多出的一位 去哪了?后面会详细说明下)
# 再将补码 转成 原码:
[0000 0000]补=[0000 0000]原 # 可以把它看成一个正数,或者是单独的0

补充:0的反码、补码 都是0
接着上面,这样0用[0000 0000]表示,而且可以用[1000 0000]表示 -128,也即是用 -128 代替了 -0 (看下面详细推导):

(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补

即是,相比较于反码和原码要将最高位当做一个符号位的做法,采取补码可以多表示一位(之前我们认为1111 1111是 -127 它最小也只能表示这么多了,因为最高位是符号位,剩余位数都为1了)!

同时:使用补码,不仅仅修复了0的符号以及存在两个编码的问题,而且还能够多表示一个最低数;这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127];而使用补码表示的范围为[-128, 127];
因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-231, 231-1] 因为第一位表示的是符号位,而使用补码表示时又可以多保存一个最小值.

(5)总结:
原码+反码:8位原码和反码能够表示数的范围是 [-127–127];
补码:8位补码能够表示数的范围是 [ -128 – 127]
(在补码中用 -128代替了-0,所以补码的表示范围为:(-128-0-127)共256个)

(以上内容参考:https://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html 进行简化和修改补充)

Python中负数的处理

  • 先补充 Python中求二进制数的内置函数:bin() 以及int() 的用法
    (1)内置函数 bin() 返回一个整数 int 或者长整数 long int 的二进制表示。
>>>bin(10)
'0b1010'
>>> bin(20)
'0b10100'

(2)内置函数 int() 用于将一个字符串或数字转换为整型。
语法格式:class int(x, base=10) ;x – 字符串或数字,base – 进制数,默认十进制。

>>>int()               # 不传入参数时,得到结果0
0
>>> int(3)
3
>>> int(3.6)
3
>>> int('12',16)        # 如果是带参数base的话,12要以字符串的形式进行输入,12 为 16进制
18
>>> int('0xa',16)
10
>>> int('10',8)
8

注意事项:
若 x–为纯数字时,不能设置base进制,否则报错

>>> int(3.1415926)
3>>> int(-11.123)
-11>>> int(2.5,10)
#报错
>>> int(2.5)
2

若 x 为 str,则 base 可略可有。
base 存在时,视 x 为 base 类型数字,并将其转换为 10 进制数字。
若 x 不符合 base 规则,则报错。

>>>int("9",2)  #报错,因为2进制无9
>>> int("9")
9 #默认10进制>>> int("3.14",8)
>>> int("1.2")
#均报错,str须为整数
>>>int("1001",2)
9
# "1001"才是2进制格式,并转化为十进制数字9
>>> int("0xa",16)
10
# ≥16进制才会允许入参为a,b,c...
>>> int("b",8) #报错
>>> int("123",8)
83
# 视123为8进制数字,对应的10进制为83
  • Python 对于负数的存储方式和 c++/c/java 有点不一样,和上面我们说的理论有点不一样!
    先看下面的例子:
a1 = bin(-3)
print(a2)a2 = bin(3)
print(a2)b = bin(-3 & 0xffffffff)
print(b)c = bin(0xfffffffd)
print(c)'''输出结果分别为:
-0b11 # 特别注意这个
0b11
0b11111111111111111111111111111101
0b11111111111111111111111111111101
'''

(1)注意,此时Python的坑出现了,首先我们知道Python 中的整型也是用 补码形式存储的,Python 中 bin 一个正数(十进制表示)结果就是它的二进制补码表示形式(看上面的a2结果);但是Python 中 bin 一个负数(十进制表示),输出的是它的原码的二进制表示加上个负号,这显然不是我们要求的负数的补码,因为我们可以求出 -3的补码应该为:ob11111111111111111111111111111101,而不应该是-0b11 ,下面将详细将Python中遇到负数要如何处理!
(2)Python 中 bin 一个负数(十六进制表示),输出的是对应的二进制表示,把上面的例子拿下来↓↓↓

c = bin(0xfffffffd) # 看上图
print(c)
# 结果为:0b11111111111111111111111111111101 (补码)
#(上图中:4294967293不是真值,它是不考虑最高符号位,直接得出的二进制值,这个可以不看)
# 将上面结果的补码,转换成原码为:0b10000000000000000000000000000011 刚好就是-3
# 再来用下 内置方法 int()
c = bin(0xfffffffd)
print(c)
# c='0b11111111111111111111111111111101' (补码)
c1 = '0b10000000000000000000000000000011' (c的原码)
print(int(c, base=2)) # 将c的原码直接用int算,又会出现一个坑,Python的int()也不认符号位,如下结果
'''输出结果
0b11111111111111111111111111111101
2147483651 # 这不是真值
'''
# 所以我们也不要用 int()直接去将负数的二进制数还原成10进制,下面会将快捷方法
  • 那问题又来了,怎么得到一个负数的十六进制数表示方式?
    答案是: 负数 & 0xFFFF FFFF 就可以得到这个负数的十六进制表示
    我们来推理一下:
11111111  11111111  11111111  11111101 (-3 的 补码)
&
11111111  11111111  11111111  11111111 (0XFFFFFFFF)
=
11111111  11111111  11111111  11111101 (二进制 补码)
# 刚好可以换成 十六进制的
0xFFFFFFFD
# 所以按照上面的推导
b = bin(-3 & 0xffffffff) # 这一步相当于bin(0xfffffffd)
print(b)
# ob11111111  11111111  11111111  11111101 (-3 补码)
# 虽然绕了一圈,但是总算是解决了Python负数的正确表示形式,请看下面的总结↓↓↓
  • 总结:
    (1)Python中,所以为了:获得负数(十进制表示)的补码,需要手动将其和十六进制数 0xffffffff 进行按位与操作,得到结果也是个十六进制数,再交给 bin() 进行输出,得到的才是你想要的补码表示。
    (2)当然讲到这里,有一道Python中的算法题:求二进制中1的个数,的做法对于c++程序和python程序的区别(负数补码的区别)
def get_num(n):count = 0if n < 0:n = n & 0xffffffff  # 得到负数的补码表示形式while n:count += 1n = n & (n - 1)return countprint(get_num(2)) # 1
print(get_num(-1)) # 32

(3)但是此时不可以直接将补码扔给 int()函数来转成十进制,上面结果已经得出了,int()也会忽略符号位!那么负数的补码如何转成十进制呢?看下面详解

负数的补码如何转成十进制

(1)当然我们可以直接将补码还原成原码,然后求出十进制值,这样做需要三步,而且不适用于代码运算中,**数值在[-256,-1]**之间的负数,我们可以用下面的快捷方式来解答↓↓↓
(2)算出补码的最后8位,直接在二进制中的数 减去 256 即可!嗯?一脸懵逼,为什么这样可以?我们来举例说明:
还是以上面的 -3 上面的补码为例

 ob11111111  11111111  11111111  11111101 (-3 补码)# 我们先取它的最后8位即是 11111101# 将最后8位 直接忽略符号位,算出它的值为:253# 让后用 253-256=-3 神奇啊,Python居然可以这样


(3)所以我们总结下数值在[-256,-1]之间的负数,求负数补码转换成十进制的方式:

c = bin(-3 & 0xffffffff) # 模拟一个负数
# c ='0b11111111111111111111111111111101' 模拟一个负数
print(int(c[-8:], base=2) - 256) # 取最后8位,然后忽略符号位求出值再减去256
# -3

(4)原理:结合我们上面到现在所有的知识点,我们选择了以补码的形式来存储(运算),如果直接无视符号位,8位二进制数结果就是所以位数的相加的和,它的最大值就是11111111=255,如果带上符号‘1’1111111,它的补码是‘1’0000001结果是 -1,为了能真正得到这个-1,我们必须让 255-256,所以在这个在[-256,-1]之间的负数,可以使用这个原理!

位运算符 和 移位运算符

基本概述

  • 位运算符 和 移位运算符
a = 60            # 60 = 0011 1100
b = 13            # 13 = 0000 1101
c = 0c = a & b;        # 12 = 0000 1100
print ("1 - c 的值为:", c)c = a | b;        # 61 = 0011 1101
print ("2 - c 的值为:", c)c = a ^ b;        # 49 = 0011 0001
print ("3 - c 的值为:", c)c = ~a;           # -61 = 1100 0011
print ("4 - c 的值为:", c)c = a << 2;       # 240 = 1111 0000
print ("5 - c 的值为:", c)c = a >> 2;       # 15 = 0000 1111
print ("6 - c 的值为:", c)'''输出结果
1 - c 的值为: 12
2 - c 的值为: 61
3 - c 的值为: 49
4 - c 的值为: -61
5 - c 的值为: 240
6 - c 的值为: 15
'''

特别注意:所有的操作都是在补码的基础上来操作的,正数当然原码、反码、补码一样不用加以考虑,负数就要注意了,一定要先求出它的补码,再来进行位运算和移位运算的操作,如下例子:

# 求 ~ -5 (给 -5 取反)
# 步骤:先求出 -5的补码
1000 .... 0101 (原码) # 总共有32位,为了方便写中间就省略了
1111 .... 1010 (反码)
1111 .... 1011 (补码)
# 然后进行 取反操作(取反操作不分符号位,即对所有位都取反)
1111 .... 1011 (补码)
~
0000 .... 0100 (结果符号位为正数,所以可以直接表示为:4)
# 求 -3 ^ 3 (求 -3 异或 3)
# 步骤:先求出 -3 补码
1000 .... 0011 (原码)
1111 .... 1100 (反码)
1111 .... 1101 (补码)
# 再来完成和 3 的异或
1111 .... 1101 (-3 的补码)
^
0000 .... 0011 (3 的补码)
=
1111 .... 1110 (两者异或的结果)
# 看上面的结果,符号位为1是负数,我们还要将它转换成 原码 才能计算出它的值
1111 .... 1110 (补码)
1111 .... 1101 (反码)
1000 .... 0010 (原码)
# 所以最后-3 ^ 3 异或的结果为 -2
  • 剖析 移位运算符

(1)<< 左移运算符
将运算对象的 各二进制位 全部左移若干位:符号位不变,低位(右边)补 0

11 << 2 = 44
# 详细过程
00000000 00000000 00000000 00001011
<< 2
00000000 00000000 00000000 00101100 (因为最高位是0,它表示一个正数)
=
44
————————————
-14 <<2 =-56
# 详细过程
11111111 11111111 11111111 11110010 (-14的补码)
<< 2
11111111 11111111 11111111 11001000 (补码)
# 看上面的结果,符号位为1是负数,我们还要将它转换成 原码 才能计算出它的值
11111111 11111111 11111111 11001000 (补码)
11111111 11111111 11111111 11000111 (反码)
10000000 00000000 00000000 00111000 (原码)
# 所以最后结果为:
-56

左移,对于正数来说,左移多少位等于 乘以 2 ^ (左移位数);左移1 相当于乘以2(但效率比乘法高)

(2)>> 右移运算符
将运算对象的 各二进制位 全部 右移若干位:低位溢出,符号位不变,并用符号位补溢出的高位

4 >> 2 = 1
# 详细过程
00000000 00000000 00000000 00000100
>> 2
00000000 00000000 00000000 00000001(因为最高位是0,它表示一个正数)
# 所以最后结果为:
1
————————————
-14 >> 2 = -4
# 详细过程
11111111 11111111 11111111 11110010 (-14的补码)
>> 2
11111111 11111111 11111111 11111100 (补码)
# 看上面的结果,符号位为1是负数,我们还要将它转换成 原码 才能计算出它的值
11111111 11111111 11111111 11111100 (补码)
11111111 11111111 11111111 11111011 (反码)
10000000 00000000 00000000 00000100 (原码)
# 所以最后结果为:
-4

右移,对于正数来说,右移多少位等于 除以 2 ^ (右移位数);右移 1 相当于除以2(效率比除法高哦)

妙用

  • 整形变量值互换
# 给定两个整形变量a、b要求在不使用其他变量的情况下,完成两个变量值得交换
# 当然 Python可以直接 a,b=b,a 本质是因为Python中的‘=’ 不是直接赋值,而是内存地址的引用;
# 但是其他语言 像java 要完成的话,需要借助一个中间变量 来完成交换,这里学了位运算可以妙用一下
a = 1
b = 2
a = a ^ b
b = a ^ b
a = a ^ b
print(a, b) # 2 1
# 连续 3 次 异或 操作便可以互换两变量的值
  • 最低位清零
# x&(x-1) 该操作可以把 x 二进制形式中最低位的1转化成0 例如:
x=0b1010110
x-1=0b1010101
x&(x-1)
1010110
&
1010101
=
1010100
  • 获取最低位的1
x = 0b01010110
x1 = x & ~(x - 1)
if x1 == 0b00000010:print('true')
print(bin(x1))
print(x1)
'''
true
0b10
6
'''
  • 交换指定位置的两个比特位
def swapBit(x, i, j):# 如果第i位和第j位上的数值相同那就没必要进行操作if ((x>>i) & 1) != ((x>>j) & 1):x ^= ((1<<i) | (1<<j))return x

二进制涉及的算法

  • 不用加减乘除做加法
  • 二进制中1的个数
  • 数组中只出现一次的数字

数据结构与算法--二进制详解 Python二进制算法详解 史上最详细的二进制讲解 彻底搞懂原码、反码、补码 Python的负数二进制表示形式相关推荐

  1. python原码反码补码

    python原码反码补码 1. 计算机计算的逻辑 2. 原码反码补码 2.1 正数: 2.2 负数 2.3 原码与补码之间的转换 2.3.1 原码->补码 2.3.1 补码->原码 2.3 ...

  2. 二进制原码反码补码详解

    二进制原码反码补码 首先我们在了解什么是原码,反码,补码之前,我们先来谈谈为什么需要有这些,只要原码不行吗? 答案肯定是不行的!

  3. 原码, 反码, 补码 详解(转载)

    原码, 反码, 补码 详解(转载) 繁星*墨菲 于 2020-05-23 10:22:53 发布1015 收藏 75 版权 转载地址:原码, 反码, 补码 详解 - ziqiu.zhang - 博客园 ...

  4. 关于计算机中 原码, 反码, 补码 详解

    本篇文章讲解了计算机的原码, 反码和补码. 并且进行了深入探求了为何要使用反码和补码, 以及更进一步的论证了为何可以用反码, 补码的加法计算原码的减法. 论证部分如有不对的地方请各位牛人帮忙指正! 希 ...

  5. 原码, 反码, 补码, 移码 详解

    本篇文章讲解了计算机的原码, 反码和补码. 并且进行了深入探求了为何要使用反码和补码, 以及更进一步的论证了为何可以用反码, 补码的加法计算原码的减法. 论证部分如有不对的地方请各位牛人帮忙指正! 希 ...

  6. 原码 反码 补码 详解

    一. 机器数和真值 在学习原码, 反码和补码之前, 需要先了解机器数和真值的概念. 1.机器数 一个数在计算机中的二进制表示形式,  叫做这个数的机器数.机器数是带符号的,在计算机用一个数的最高位存放 ...

  7. 原码 反码 补码 详解

    本篇文章讲解了计算机的原码, 反码和补码. 并且进行了深入探求了为何要使用反码和补码, 以及更进一步的论证了为何可以用反码, 补码的加法计算原码的减法. 论证部分如有不对的地方请各位牛人帮忙指正! 希 ...

  8. 【程序员必修数学课】-基础思想篇-二进制-原码反码补码的数学论证

    二进制计数法&原码&反码&补码 Ⅰ 前言 Ⅱ 二进制计数法 A. 什么是二进制计数法? B. 为什么要使用二进制? C. 二进制的位运算 D. 符号位 E. 溢出 Ⅲ 原码&a ...

  9. 原码, 反码, 补码详解——北大陈向群老师课堂笔记

    一. 机器数和真值 在学习原码, 反码和补码之前, 需要先了解机器数和真值的概念. 1.机器数 一个数在计算机中的二进制表示形式, 叫做这个数的机器数.机器数是带符号的,在计算机用一个数的最高位存放符 ...

最新文章

  1. MindSpore静态图语法支持
  2. linux 跟阿铭学linux
  3. TikTok英国市场你不能不知道的10大数据
  4. bat 服务启动脚本
  5. arraylist线程安全吗_java集合----超详细图解(ArrayList线程安全解决三种解决方法!)...
  6. UNIX再学习 -- 函数 system
  7. 如何在不跳转的情况下实现用户登录
  8. c语言求今年第m月的天数,《C语言及程序设计》实践参考——当年第几天(数组方案)...
  9. 坦克乘员协同训练模拟系统
  10. Python基础 —— dict
  11. 引用和使用引用传递参数《一》
  12. Entity Framework 延伸系列目录
  13. 中国妇女儿童统计资料(2014-2020年)
  14. Python精讲:Python中集合的概念和创建方法详解
  15. 8cm等于多少像素_厘米与像素一张图片,要打印尺寸为10.8厘米*17厘米,图片要编 爱问知识人...
  16. 百度地图 和百度导航及语音集成冲突解决办法
  17. cz.cc免费域名申请教程(因为有朋友不知道怎么操作,特写此简单教程)
  18. java中jsp是什么_JSP是什么?
  19. 用计算机打字教案,教学设计-有趣的打字练习游戏
  20. 由“戴尔用博客与中国用户沟通”想起

热门文章

  1. 命名规范与注释规范概述
  2. 5个普通人与腾讯位置服务的故事,看看里面有你的影子吗?
  3. php幻灯片的插件,萧涵主题添加幻灯片插件WP flash img show
  4. 高清图文全程拆解更换iPhone屏幕过程
  5. 这是一篇男女老少入门精通咸宜的正则笔记
  6. Linux下按照行数切割文件
  7. win10禁用驱动程序强制签名
  8. linux欧拉强制修改root密码,openEuler 20.03 LTS安装图文教程
  9. Navicat Premium 15 安装教程
  10. PHP截取字符串长度