python位运算和float浮点型底层存储原理

一 前言

位运算,一个极容易被低端码农忽视的地带,因为它略微需要用一丢丢智商,真的只有那么一丢丢,但高手与low手的差距往往就是长这么一丢丢,能达到的深度就截然不同了

二 真值、机器数(原码、反码、补码)

  1. “真值”指的就是数本身,例如-10,真值就是-10
  2. 一个数在计算机中的二进制表示形式,叫做这个数的机器数
  3. 在计算机中,用来表示有符号数的机器数有三种,即原码、反码、补码
  4. 三种表示方法均有“符号位”和“数值位”两部分
    1. 符号位都是占据最高位,用0表示“正数”,用1表示“负数”
    2. 数值位,三种表示方法各不相同。

整型数字有8位、16位、32位、64位几种,先单以8位整型为例来介绍

问:8位二进制数可以表示的数值范围是多少,99%的人张口就来:-128~127

8位二进制数,最高位需要用来表示符号,那么剩下的7位用来表示数值,于是,最大数为11111111=127,最小01111111=-127,得出的错误结论是:8位二进制数可以表示的数值范围是-127到+127。

灵魂拷问:128到底怎么来的???

真相是这样的:

8位二进制数用7位表示数值,那么7位2进制数0000000的值为0,那么,它前面加上符号位0还表示0吧?

如果它前面加上1呢,仍然表示0?这不是重复了么?一个0怎么用两个值来表示呢!!!

所以10000000就表示-128,ok,我们接下来就在8位二进制数,即-128~127的范围内取值来介绍它的原码、反码、补码

正数:原码、反码、补码都一样

真值:3
原码:0000 0011 最高位为0,表示正数
反码:0000 0011
补码:0000 0011

负数:原码、反码、补码不同

真值:-3
原码:1000 0011 最高位为1,表示负数
反码:1111 1100 由原码演变而来,原码的符号位不变,数值位全部取反
补码:1111 1101 在反码的基础上+1

在这里插入图片描述

在计算机系统中,数值一律用补码来存储 !!! ​

主要原因:使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理,需要注意的是两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃,处理完后我们如上图所示用补码反推出真值即可,例如计算机在计算8-3的时候,会这么做8+(-3),具体如下

  1. 第一步:真值->原码->反码->补码
    真值:8
    原码:00001000
    反码:00001000
    补码:00001000

    真值:-3
    原码:10000011
    反码:11111100
    补码:11111101

  2. 第二步:补码之间的运算,此处为相加
    8的补码:00001000
    -3的补码:11111101
    相加得补码:00000101 (补码相加,高位有进位会被舍弃)

  3. 第三步:补码->反码->原码->真值
    上一步得到的补码结果:00000101
    符号位是0,为正数,那么就简单了,正数的原、反、补码都一样,所以一步到位
    补码->反码->原码:00000101 原码->真值:5

练习:8+(-9)

  1. 第一步:真值->原码->补码
    真值:8
    原码:0000 1000
    反码:0000 1000
    补码:0000 1000

    真值:-9
    原码:1000 1001
    反码:1111 0110
    补码:1111 0111

  2. 第二步:补码之间的运算,此处为相加
    8的补码:0000 1000
    -9的补码:1111 0111
    相加得补码:1111 1111

  3. 第三步:补码->反码->原码->真值
    补码->反码
    补码结果:1111 1111
    符号位是1,为负数,参照上图2的步骤

    补码->反码:-1,得到反码:1111 1110

    反码->原码:符号位不变,其余位取反,得到原码:1000 0001

    原码->真值:-1

三 位运算

  1. 按位与&:两位全为1,结果才为1,否则为0
  2. 按位或|:两位只要存在一个1,结果就为1,否则为0
  3. 按位异或^:只有在两位不相同,即一个为0一个为1的情况下,结果才为1,否则为0
  4. << n:各二进制位全部左移n位,高位丢弃,低位补0
  5. >> n: 各二进制位全部右移n位,如果是正数,则高位补0,如果是负数则高位补1

3.1 按位与

示例1:8 & -3

  1. 第一步:真值->原码->反码->补码
    真值:8
    原码:0000 1000
    反码:0000 1000
    补码:0000 1000

    真值:-3
    原码:1000 0011
    反码:1111 1100
    补码:1111 1101

  2. 第二步:补码之间的运算,此处为&
    8的补码:0000 1000
    -3的补码:1111 1101
    &得补码:0000 1000

  3. 第三步:补码->反码->原码->真值
    上一步得到的补码结果:0000 1000
    符号位是0,为正数,那么就简单了,正数的原、反、补码都一样,所以一步到位
    补码->反码->原码:0000 1000
    原码->真值:8

示例2:-8 & -9

  1. 第一步:真值->原码->补码
    真值:-8
    原码:1000 1000
    反码:1111 0111
    补码:1111 1000

    真值:-9
    原码:1000 1001
    反码:1111 0110
    补码:1111 0111

  2. 第二步:补码之间的运算,此处为&
    -8的补码:1111 1000
    -9的补码:1111 0111
    &得补码:1111 0000

  3. 第三步:补码->反码->原码->真值
    补码->反码
    补码结果:1111 0000
    符号位是1,为负数,参照上图2的步骤
    补码->反码:-1,得到反码:1110 1111
    反码->原码:符号位不变,其余位取反,得到原码:1001 0000
    原码->真值:-16

3.2 按位或|

示例1:-8 | -9

  1. 第一步:真值->原码->补码
    真值:-8
    原码:1000 1000
    反码:1111 0111
    补码:1111 1000

    真值:-9
    原码:1000 1001
    反码:1111 0110
    补码:1111 0111

  2. 第二步:补码之间的运算,此处为|
    -8的补码:1111 1000
    -9的补码:1111 0111
    |得补码:1111 1111

  3. 第三步:补码->反码->原码->真值
    补码->反码
    补码结果:1111 1111
    符号位是1,为负数,参照上图2的步骤
    补码->反码:-1,得到反码:1111 1110
    反码->原码:符号位不变,其余位取反,得到原码:1000 0001
    原码->真值:-1

3.3 按位异或^

示例1:-8 ^ -9

  1. 第一步:真值->原码->补码
    真值:-8
    原码:1000 1000
    反码:1111 0111
    补码:1111 1000

    真值:-9
    原码:1000 1001
    反码:1111 0110
    补码:1111 0111

  2. 第二步:补码之间的运算,此处为^
    -8的补码:1111 1000
    -9的补码:1111 0111
    ^得补码:0000 1111

  3. 第三步:补码->反码->原码->真值
    上一步得到的补码结果:0000 1111
    符号位是0,为正数,那么就简单了,正数的原、反、补码都一样,所以一步到位
    补码->反码->原码:0000 1111
    原码->真值:15

示例2:^ -8 单独一个^代表取反的意思(适用于go,不适用于python)

  1. 第一步:真值->原码->补码

    真值:-8
    原码:1000 1000
    反码:1111 0111
    补码:1111 1000

  2. 第二步:
    -8的补码:1111 1000
    ^取反得补码:0000 0111

  3. 第三步:补码->反码->原码->真值
    上一步得到的补码结果:0000 0111
    符号位是0,为正数,那么就简单了,正数的原、反、补码都一样,所以一步到位
    补码->反码->原码:0000 0111
    原码->真值:7

3.4 向左位移<< n

示范1:-8 << 3

 
  1. 第一步:真值->原码->补码
    真值:-8
    原码:1000 1000
    反码:1111 0111
    补码:1111 1000

  2. 第二步:<< n 各二进制位全部左移n位,高位丢弃,低位补0
    补码:1111 1000
    <<3: 1100 0000

  3. 第三步:补码->反码->原码->真值
    补码->反码
    补码结果:1100 0000
    符号位是1,为负数,参照上图2的步骤
    补码->反码:-1,得到反码:1011 1111
    反码->原码:符号位不变,其余位取反,得到原码:1100 0000
    原码->真值:-64

3.5 向右位移>> n

示范1:-8 >> 3

 
  1. 第一步:真值->原码->补码
    真值:-8
    原码:1000 1000
    反码:1111 0111
    补码:1111 1000

  2. 第二步:>> n 各二进制位全部右移n位,如果是正数,则高位补0,如果是负数则高位补1
    补码:1111 1000
    >>3: 1111 1111

  3. 第三步:补码->反码->原码->真值
    补码->反码
    补码结果:1111 1111
    符号位是1,为负数,参照上图2的步骤
    补码->反码:-1,得到反码:1111 1110
    反码->原码:符号位不变,其余位取反,得到原码:1000 0001
    原码->真值:-1

示范2:8 >> 3

  1. 第一步:真值->原码->补码
    真值:8
    原码:0000 1000
    反码:0000 1000
    补码:0000 1000
  2. 第二步:>> n 各二进制位全部右移n位,如果是正数,则高位补0,如果是负责则高位补1
    补码:0000 1000
    >>3: 0000 0001
  3. 第三步:补码->反码->原码->真值
    上一步得到的补码结果:0000 0001
    符号位是0,为正数,那么就简单了,正数的原、反、补码都一样,所以一步到位
    补码->反码->原码:0000 0001
    原码->真值:1

示范3:-300 >> 8

  1. 第一步:真值->原码->补码
    真值:-300
    原码:1000 0001 0010 1100 # -300 已经超过了8位二进制能表示的范围,需要用16位表示
    反码:1111 1110 1101 0011
    补码:1111 1110 1101 0100

  2. 第二步:>> n 各二进制位全部右移n位,如果是正数,则高位补0,如果是负责则高位补1
    补码:1111 1110 1101 0100
    >>8: 1111 1111 1111 1110

  3. 第三步:补码->反码->原码->真值
    补码->反码
    补码结果:1111 1111 1111 1110
    符号位是1,为负数,参照上图2的步骤
    补码->反码:-1,得到反码:1111 1111 1111 1101
    反码->原码:符号位不变,其余位取反,得到原码:1000 0000 0000 0010
    原码->真值:-2

四 位运算高级操作

位运算是 cpu 直接支持的,效率最高,位运算可能在平常的编程中使用的并不多,但涉及到底层优化,一些算法及源码可能会经常遇见,下面来介绍一下风骚的操作。

4.1 用位运算 & 取代 % 取模

X % 2^n = X & (2^n  1)
注意:用位运算 & 来取代 % 取模需要被取模的数必须是2的幂才成立

示范1:

10 % (2^3) 等于 10 & (2^3-1)

10 % 8 等于 10 & 7

示范2:

10 % (2^2) 等于 10 & (2^2-1)

10 % 4 等于 10 & 3

4.2 将一个数左移 n 位,相当于乘以了 2 的 n 次方,右移n位,相当于除以2的n次方取整

10 << 3 等同于10 * 2^3 
10 >> 3 等同于10 / 3

4.3 判断奇偶

我们可以利用 & 运算符的特性,来判断二进制数第一位是0还是1
if ((a & 1) == 0) 代替 if (a % 2 == 0)来判断a是不是偶数

4.4 交互数值

1、借助临时变量来交互数值
a:=10
b:=20
temp:=a
a=b
b=temp
fmt.Println(a,b) // 结果20 10
2、借助累加和
如果考虑到内存,不希望使用临时变量(其实就是为了炫酷),可以这样实现:
a:=10
 b:=20
 a = a + b
 b = a - b
 a = a - b
 fmt.Println(a,b) // 结果20 10
从数学角度来分析一下
- 第一步:a = a + b
- 第二步:b = a - b = (a + b) - b = a
- 第三步:a = a - b = (a + b) - b = (a + b) - a = b
3、使用 ^ 位运算符
如果想要更炫酷一点可以使用 ^ 来帮忙实现:
先来了解一下 ^ 的几个特性:
a ^ a = 0
a ^ 0 = a
(a ^ b) ^ c = a ^ (b ^ c)
代码:
a:=10
b:=20
a ^= b;
b ^= a;
a ^= b;
fmt.Println(a,b) // 结果20 10
从数学角度来分析一下:
- 第一步:a = a ^ b
- 第二步:b = a ^ b = (a ^ b) ^ b = a ^ (b ^ b) = a ^ 0 = a
- 第三步:a = a ^ b = (a ^ b) ^ b = (a ^ a) ^ b = b ^ 0 = b

4.5 项目中的应用

在项目中可以用位运算进行一些状态的运算,效率极其高,例如:现在我们有一些爱好需求,这些爱好有 足球 羽毛球 乒乓球 篮球 游泳 如果按照整数的形式去定义,那就有无数多种情况,毕竟可以两两组合嘛,python中的列表、go中的数组都不是最佳选择

可以直接用二进制位表示,1代表爱好,0代表没有该爱好

爱好:只有足球
表示:1 0 0 0 0
爱好:乒乓球、游泳
表示:0 0 1 0 1

接下来就可以用位运算进行一些风骚的操作啦,例如

应用1:检查是否存在某个爱好

喜欢足球的状态是:1 0 0 0 0 
小明的喜好:0 1 0 1 0
&计算的结果为:0 0 0 0 0,返回位false,所以小明不存在喜好足球的状态

应用2:添加某个爱好

喜欢足球:   1 0 0 0 0
小明的喜好: 0 1 0 1 0 
|运算之后: 1 1 0 1 0

float浮点型底层存储原理

在这里插入图片描述
在这里插入图片描述

浮点数转换为二进制表示

  • 整数部分,直接转换为二进制,即:100111
  • 小数部分,让小数一直乘2,小于1则用结果继续乘,大于1则结果减1继续乘,等于1则结束。
    在这里插入图片描述

科学计数法表示二进制小数

在这里插入图片描述
注意:因为是二进制小数,所以底数是2。

存储

  • Float32,用32位的二进制来存储一个浮点数。
  • Float64,用64位的二进制来存储一个浮点数。

通过对浮点型的存储原理的学习,了解到浮点型其实是一种非精确的表达小数的方式,因为他的fraction中有位数限制,超过就会忽略。

float64和float32类似,只是用于表示各部分的位数不同而已,其中:sign=1位exponent=11位fraction=52位,也就意味着可以表示的范围更大了。

### Python 数值类型的特性与应用 #### 整型 Python中的整型可以表示任意大小的整数,无论是正数、零还是负数。这一灵活性使得开发者无需像C语言那样考虑数值范围并选择`short`, `int`, 或者`long`等不同类型[^1]。 ```python # 定义一个较小的整数 small_int = 56 print(small_int) # 定义一个非常大的整数 large_int = 9999999999999999999999 print(large_int) ``` 一旦数值超出常规计算能力,Python能够自动切换到高精度模式来确保准确性[^2]。 #### 浮点型 浮点数用于表达带有小数部分的数据,在科学计算等领域极为常见。为了区分于整数,任何含有小数点的数字都会被识别成浮点数[^4]。 ```python float_num_1 = 5.12 # 带有显式的小数float_num_2 = 512.0 # 即使看起来像是整数也要加上".0" float_num_3 = .512 # 小数点前可省略0 ``` 需要注意的是,由于硬件层面的原因,浮点运算可能存在微小误差;因此对于精确度要求极高的场景应当谨慎对待。 #### 复数 复数由实部虚部组成,通常写作`real+imagj`的形式,其中`imag`代表虚数单位\( j \),它满足\( j^2=-1 \)[^3]。 ```python complex_number = 3 + 4j # 创建一个新的复数对象 another_complex = complex(7, -8) # 使用内置函数创建复数 ``` 通过上述方式定义之后可以直接访问其属性: ```python print(complex_number.real) # 获取实部 print(complex_number.imag) # 获取虚部 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Generalzy

文章对您有帮助,倍感荣幸

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值