跳至主要內容

逻辑运算符

Mayee...大约 8 分钟

前言

最近做业务需求有这么一个场景,当某一天的历史净值有对应的分红计划和复权净值调整时,需要算出这一天的净值差和累计净值,将结果追加到这一天的历史净值表中,完成后要将这一天的分红计划和复权净值调整标记为已执行(在数据库中字段存储值:1 表示已执行;0 表示未执行),也就是说这是一个原子操作,要么都成功,要么都失败。如果失败了,在下一次定时到来将被再次计算。但测试发现偶发情况下,某天的历史净值记录有追加的净值差和累计净值字段,但这天对应的分红计划和复权净值调整记录中,却不是同时被标记了已执行,即分红计划和复权净值调整这两者的标记,可能其中一个是 1 一个是 0。WTF? 当时因为有其他优先级更高的事要处理这个问题暂放,过了一个多小时再回来检查数据发现又正常了,WHAT? 这是什么鬼,什么也没做它自己又好了,但我不能当做什么也没发生,删掉数据重新初始化一遍,果然问题又出现了。这是个问题,必须得找出原因,仔细检查了业务代码逻辑,毫无破绽,那还会是什么原因呢?思来想去,我认为最大的可能性就是程序初始化时,由于同时请求了多个三方接口拿数据,拿到数据后做了不同的业务导致在入库的顺序无法保证,而这些业务又可能会查已入库的数据,查到时和没查到时的业务有些区别,这就很可能会导致数据被覆盖了。也就是说原本是正确的逻辑,正确的数值入库了,但马上又被其他同时在进行的业务覆盖了,结果数据库中看到的就是逻辑不正确的数据。OK,既然有了方向那就改呗,一顿操作后改完更新的开发环境,删掉数据再初始化一遍,然后检查刚刚有问题的数据已经正常了。这条数据是正常了,其他数据是否正常也不知道,那就查一下数据呗。筛选出来的目标数据要符合执行标记一边是 1 一边是 0 的,这个逻辑不正是异或嘛!问题来了,异或在程序中怎么运算,下面进入正文。

1. 逻辑运算符

在进行业务编码时经常会用到&&(与)||(或)!(非)三种逻辑运算,而^(异或)同或~(按位取反)比较少会用到。 我们常用 1(真) 和 0(假) 来表示逻辑,这些逻辑运算结果如下:

:两个输入全为真时输出为真,否则为假。在计算机中使用通常使用&&表示。

输入A输入B结果
000
100
010
111

Java 中有&&&两种,它们都表示与运算,运算逻辑也相同。区别是&&只能用于逻辑运算,并且会短路逻辑,即当有多个输入时,从左向右依次判断真假,如果某个输入为假则立刻输出假,不再继续判断;而&既可用于逻辑运算,也可用于位运算,当用于逻辑运算时不会短路逻辑,即当判断到假时,仍会继续向右判断。一般多使用&&,在某些特殊的场合可能会用到&。 Go 中有&&&两种,它们都表示与运算,但它们表示的意义和 Java 中有些不同。&&只能用于逻辑运算,&只能用于位运算。 Python 中有and&两种,它们都表示与运算,两者都能用于逻辑运算和位运算。同样的and会短路逻辑&不会。因为 Python 是弱类型语言,即使函数返回值定义为int也能return Ture,因为在 Python 中Ture==1

:两个输入中至少有一个为真时结果为真,否则为假。在计算机中使用通常使用||表示。

输入A输入B结果
000
101
011
111

Java 中有|||两种,它们都表示或运算,运算逻辑也相同。区别是||只能用于逻辑运算,并且会短路逻辑,即当有多个输入时,从左向右依次判断真假,如果某个输入为真则立刻输出真,不再继续判断;而|既可用于逻辑运算,也可用于位运算,当用于逻辑运算时不会短路逻辑,即当判断到真时,仍会继续向右判断。一般多使用||,在某些特殊的场合可能会用到|。 Go 中有|||两种,它们都表示或运算,但它们表示的意义和 Java 中有些不同。||只能用于逻辑运算,|只能用于位运算。 Python 中有or|两种,它们都表示或运算,两者都能用于逻辑运算和位运算。同样的or会短路逻辑|不会。因为 Python 是弱类型语言,即使函数返回值定义为int也能return False,因为在 Python 中False==0

:单个输入如果为真则输出假,反之亦反。在计算机中使用通常使用!表示。

输入结果
01
10

Java 和 Go 的用法是一致的,且只能用于逻辑运算。Python 则稍有不同,它的非用not表示,且都能用于逻辑运算和位运算。

2. 位运算符

当使用位运算时,实际是将原始数值转换为二进制按位运算。

异或:两个输入不同时输出真,相同则输出假。在计算机中使用通常使用^表示。

输入A输入B结果
000
101
011
110

Java、Python 和 Go 的用法都是一致的。

同或:两个输入相同时输出真,不同则输出假。

输入A输入B结果
001
100
010
111

同或运算没有专门的运算符,但我们可以使用a^b^1来达到同或的运算逻辑。其实同或就是异或取反,但因为这是位运算,所以不能使用逻辑运算符。

按位取反:对二进制的每一位(包括符号位)取反。在计算机中使用通常使用~表示。 我们可以简单的理解,~x=-(x+1)。即,~5=-(5+1)=-6,~-5=-(-5+1)=4。细节这里先按下不表,在下文中详细介绍。

3. 计算机与二进制

计算机中常会用到二进制(0~1)、八进制(0~7)和十六进制(0~F),其中 10~15 分别用 A~F 表示: 二进制(Binary),例:0011。 八进制(Octal),使用0开头,例:073,转换为二进制是 00111011。 十进制(Decimal),除 +/- 符号外,使用1~9开头,例:98,转换为二进制是 01100010。 十六进制(Hexadecimal),使用0x或0X开头,例:0xFA,转换为二进制是 11111010。

计算机中存储的是二进制的补码。例如,-5 转换为二进制是 1101。最高位是符号位,0表示+,1表示-。 原码:1101 反码:1010 补码:1011

负数的反码是原码除符号位外取反的结果,补码则是在反码的基础上加一。 正数的反码、补码均和原码相同。

现在说说按位取反~操作。

计算 ~4:

  • 将十进制转换为二进制是 0000 0100,因为正数的原码、反码、补码相同,因此计算机中存储的补码也是 0000 0100
  • 按位取反~是 1111 1011
  • 由于计算机存的是补码,因此需要将上一步转换为补码,但需要转换为反码。又因为 1111 1011 的最高位是1,计算机认为它是负数,则按负数的取反规则,符号位不变其余位取反是 1000 0100,补码是 1000 0101

转换为十进制是 -5

计算 ~-5:

  • 将十进制转换为二进制是 1000 0101
  • 因为计算机中存的是补码,因此需要转换为补码,但需要先转换为反码。则反码是 1111 1010,补码是 1111 1011
  • 按位取反~是 0000 0100

转换为十进制是 4

4. 结语

知道异或的符号后,赶紧写了几行 py 代码查了下数据库,所有数据都正确,OK 可以发版了。 不得不说 Python 在这种需要简单快速验证的场合挺方便的,没有数据类型限制,强类型的语言还需要写一堆类型转换,当然强类型也有它优势的地方,得灵活变通,在合适的场合选择合适的方式解决问题。