---
url: /article/liidbsu1/index.md
---
## 前言

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

## 1. 逻辑运算符

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

**与**：两个输入全为真时输出为真，否则为假。在计算机中使用通常使用`&&`表示。

|输入A|输入B|结果|
|--|--|--|
|0|0|0|
|1|0|0|
|0|1|0|
|1|1|1|

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

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

|输入A|输入B|结果|
|--|--|--|
|0|0|0|
|1|0|1|
|0|1|1|
|1|1|1|

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

**非**：单个输入如果为真则输出假，反之亦反。在计算机中使用通常使用`!`表示。

|输入|结果|
|--|--|
|0|1|
|1|0|

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

## 2. 位运算符

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

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

|输入A|输入B|结果|
|--|--|--|
|0|0|0|
|1|0|1|
|0|1|1|
|1|1|0|

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

**同或**：两个输入相同时输出真，不同则输出假。

|输入A|输入B|结果|
|--|--|--|
|0|0|1|
|1|0|0|
|0|1|0|
|1|1|1|

同或运算没有专门的运算符，但我们可以使用`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 在这种需要简单快速验证的场合挺方便的，没有数据类型限制，强类型的语言还需要写一堆类型转换，当然强类型也有它优势的地方，得灵活变通，在合适的场合选择合适的方式解决问题。
