引发
void exchange_0(int* a, int* b)
{
*a = *a ^ *b;
*b = *a ^ *b;
*a = *a ^ *b;
}
了解到这三条按位异或竟然可以实现对 a 和 b 两数的交换,深感有趣。位运算的替换经常能够在速度上优化代码,这里也不例外,自己机子上实测比用第三变量实现交换快了15%~25%。
这三条语句实在是优美,在上工图课的我不由自主地开始研究起来(别学QwQ)。
这个方法似乎还能写得更离谱一点:
void exchange_1(int* a, int* b)
{
*a ^= *b ^= *a ^= *b;
}
〇、异或运算 (XOR) 的特点
这种交换方式,基于这样一个优秀运算性质:将两数异或的结果与其中一个数再进行异或,可以得到另一个数
(
a
⊕
b
)
⊕
a
=
b
(
a
⊕
b
)
⊕
b
=
a
(a\ \oplus\ b ) \ \oplus\ a = b \\ (a\ \oplus\ b ) \ \oplus\ b = a
(a ⊕ b) ⊕ a=b(a ⊕ b) ⊕ b=a
这样的性质让你联想到了什么东西呢,我们为方便暂且称其为高度对称性,下面我们从这些角度来对这玩意展开奇思妙想。
- 其他常见逻辑运算能有高度对称性吗?——信息量上的前提
- 对称性的运算能继续推广吗?——密码加密与解码
- 异或的对称性具体是如何实现的?——二进制逻辑运算中唯一的光
- 二进制数以外还能有类似的运算吗?——编码方式与运算规则的统一
- 未解决之谜:如何在最初程序中把异或作为不进位加法理解?
一、其他常见逻辑运算能有逆向对称性吗?
位运算中的信息丢失——信息量上的前提
部分逻辑门真值表如下(同或 就是异或+非)
A B | 0 0 | 1 0 | 0 1 | 1 1 |
---|---|---|---|---|
异或 | 0 | 1 | 1 | 0 |
同或 | 1 | 0 | 0 | 1 |
与 | 0 | 0 | 0 | 1 |
或 | 0 | 1 | 1 | 1 |
要想知道其他运算有没有逆向对称性,直接去试试把运算结果与其中一个原数放一起,看是否能用该运算再得到另一个原数。
比如对 1010、0110 (包含0、1的所有组合) 进行同或、与、或,尝试用某种求逆方法求回原数(同或只是取反的异或,不用试也能想到是可行的,并且同样能实现两数交换)。
在尝试后我们发现,将 与运算 和 或运算 的结果利用其中一个原数来逆运算时,结果并不一定是惟一的。
a & b = 0 且 a = 0 则 b 有可能是 0 也有可能是 1;
a | b = 1 且 a = 1 则 b 有可能是 0 也有可能是 1。
这说明对于与、或这两个运算,已知结果与一个参数是可能无法确切求出另一参数的,在运算过程中似乎会丢失一些信息,让我们回到真值表上看看。
在0与1的四种组合中,与运算 结果有3个 '0’和1个 ‘1’,相应地,或运算 结果有3个 '1’和1个 ‘0’,而异或、同或结果都是2个 '0’和 2个 ‘1’。
已知结果的情况下,每多知道一些信息(比如知道一个原数),就能够限制原数组合的可能性,向着求出原始数据的方向前进一步。异或与同或的每种结果只对应两种原始数据组合,所以只知其一即可求解;而 与运算的0结果、或运算的1结果都对应三种组合,都无法在只知其一的情况下将可能性"坍塌"到唯一确定的情况。
抽象地理解起来,就是 &、| 会将原始数据的信息进行压缩,导致信息丢失。将3种数据组合压缩至1种结果,而在求逆时参数信息(其中一个原数)总共也就0和1两种情况,自然无法由1变回3。从这个角度思考,异或、同或的对称性可以延伸到信息不丢失的性质。
数值交换的新思路
我们所熟知的数值交换是这样的:
void exchange_00(int* a, int* b)
{
int c = *a
*a = *b;
*b = c;
}
因为赋值过程同样是覆盖原值的过程,我们需要申请第三个数据空间以保留被覆盖值,否则 a 的值的信息就丢失了。
而现在我们了解到异或运算并不会丢失信息,还是高度对称的,也就意味着 a , b , a^b 三者只要知道二者就能求得第三者。那么我们可以保证只用到两个变量,保证每次赋值后都储存三者之二(有点在三个数之间轮换的感觉),以实现不借用第三空间的数值交换。我们具体来看看这个轮换赋值的过程:
语句 | A的值 | B的值 |
---|---|---|
*a = *a ^ *b | a^b | b |
*b = *a ^ *b | a^b | a |
*a = *a ^ *b | b | a |
真不戳~
二、对称性的运算能继续推广吗?
我们所熟知而不知的对称性
不知读者是否想把对称性推广至逻辑运算以外的运算,至少我是想的。我们先在数学上理解清楚这种对称性:定义一个二元运算 m(a, b),要想其能担任 异或 交换数值的职位,则该运算需要有以下特点:
{
1.
满
足
交
换
律
m
(
a
,
b
)
=
m
(
b
,
a
)
2.
存
在
逆
运
算
m
[
m
−
1
(
c
,
a
)
,
a
)
]
=
c
3.
双
向
可
求
逆
{
m
−
1
[
m
(
a
,
b
)
,
a
]
=
b
m
−
1
[
m
(
a
,
b
)
,
b
]
=
a
\begin{cases} 1.满足交换律 && m(a,b)=m(b,a) \\ 2.存在逆运算 && m[m^{-1}(c,a),a)]=c \\ 3.双向可求逆 && \begin{cases}m^{-1}[m(a,b),a]=b \\ m^{-1}[m(a,b),b]=a \end{cases} \end{cases}
⎩⎪⎪⎪⎨⎪⎪⎪⎧1.满足交换律2.存在逆运算3.双向可求逆m(a,b)=m(b,a)m[m−1(c,a),a)]=c{m−1[m(a,b),a]=bm−1[m(a,b),b]=a
PS:实际上(1)与(3)是等价的
我们似乎在把问题复杂化,但将对称性的数学特点提炼出来有助于我们找到更多类似的运算。比如交换律自然能联想到加法和乘法,其逆运算分别是减法与除法,于是我们找到了两数交换的另两种方式。(实际上加减法的实现更令人熟知,但这样两个习以为常的符号确实很难引起我们的深入思考)
a = a+b;
b = a-b;
a = a-b;
a = a*b;
b = a/b;
a = a/b;
这里其实存在两种直观上的对称性:
1.交换律的对称性:两个运算对象位置的对称性
2.可逆的对称性:结果与原始数据的对称性
而异或、同或运算甚至还要更高一层:它们的逆运算就是本身。
可逆性也算半个对称性——密码加密与解码
我们放宽一点标准,只 要求一个运算能有上面的第二条特点即可,一个二元运算存在与之对应的二元逆运算,以及前面各种性质的分析,你是否联想到了密码呢?
有明文 P、密钥 K 和密文 C,利用 K 将 P 转化为 C 的加密过程相当于 m(P,K) = C,而解码过程相当于 m^{-1}(C,K) = P。
于是最简单的加减法也能进行加密。你想告诉你同学你一天 ** 2次,而你们事先都知道密钥是7,那么你为了防止你的"重要信息"被泄露就可以用加法加密:C = K+P = 2+7 = 9,然后告诉你同学你一天 ** 9 次,他自然能用加法的逆运算解码:P = C-K = 2,从而得知你一天 ** 2次,而旁人只会投来敬佩的眼神。
没错你一天刷牙2次,个人卫生做得还不错。( ̄▽ ̄)
那么加上交换律就相当于明文与密钥是相互等价的,而异或更是相当于加密与解码方式完全相同。
三、异或的高度对称性具体是如何实现的?
从底向上枚举逻辑运算————二进制逻辑运算中唯一的光
加密与解码方式完全相同,自己就是自己的逆运算,这异或运算是越来越神奇了。
但这种性质似乎是 异或 与生俱来的本领,我们不如暂时避开对 异或 的解构,而从这种性质入手看看在所有枚举的逻辑运算中,是否有同样性质者。
上文已经说明,要想保留信息实现可逆,运算结果必须是两个 ‘1’ 两个 ‘0’,共枚举出6种情况。(实际上后三都是前三的非,而非运算自己也是自己的逆运算,所以本质上就3种)
运算种类 | ||||
---|---|---|---|---|
A B | 1 1 | 0 1 | 1 0 | 0 0 |
运算F1 | 1 | 1 | 0 | 0 |
运算F2 | 1 | 0 | 1 | 0 |
同或 | 1 | 0 | 0 | 1 |
异或 | 0 | 1 | 1 | 0 |
运算F3 (F2非) | 0 | 1 | 0 | 1 |
运算F4 (F1非) | 0 | 0 | 1 | 1 |
列举完了我们试试把它们强硬地应用自身求逆:
震惊!图中我们可以看到 F1、F2 不仅仅是不满足自己是自己的逆运算,甚至连用其他方法回到原始数据都不可能:F1 与 B 的前两位组合都是相同的,但我们要求出的 A 前两位却是不同的。用上文的话说,这里发生了信息丢失!
新的信息丢失形式
明显地我们看到同或那边没问题,而 F1、F2 在形式上与其最大的差别就是 F1、F2 的运算结果会与其中一个原数相同,相当于复制了其中一个原数而丢弃了另一个。于是如果运气不好,知道的是结果和那个一模一样的原数,自然无法求出另一原数。
为了把形式扩展到 F3(F2非)、F4(F1非),我们再提炼一步。
对于 A 和 B 中的任意两位(任意两个01的组合),如果 A 的这两位相同 或 B 的这两位相同,则运算结果必须不同,否则信息会丢失。
异或与同或的"自逆"原理
在通过排除后我们知道了唯有 异或 和 同或 能实现信息完全不丢失,但这并没有解答为什么它们本身就是自己的逆运算。为什么不是异或与同或互逆呢?
我们通过排除知道了唯有 异或 和 同或 能实现信息完全不丢失,但这并没有解答为什么它们本身就是自己的逆运算。为什么不是异或与同或互逆呢?
为
什
么
不
是
这
样
呢
。
。
。
(
a
⊕
b
)
⊙
a
=
b
(
a
⊙
b
)
⊕
b
=
a
为什么不是这样呢。。。 \\ (a\ \oplus\ b ) \ \odot\ a = b \\ (a\ \odot\ b ) \ \oplus\ b = a
为什么不是这样呢。。。(a ⊕ b) ⊙ a=b(a ⊙ b) ⊕ b=a
在上文的各方面探索后,我们回来看看运算的定义,异或、同或的竖式运算变得格外优美~
1010
1100
_
_
_
_
_
_
_
_
⊙
1001
⊕
0110
\ 1 010 \\ \ 1100 \\ \_\_\_\_\_\_\_\_\\ \odot\ 1001\ \ \\ \oplus\ 0110\ \ \\
1010 1100________⊙ 1001 ⊕ 0110
直观理解上,它们有一种"交错"的感觉,似乎可以相互调换而不产生影响。
而我们回来看看所谓的"自逆"性质,其实就是在竖式中结果可以与其中一个原数进行交换:
注意到其实我们选择1010和1100这两个数其实并不是随意挑出来的,四位各代表01的四种组合,也就是说这四位之间没有固定顺序。
于是在上下交换过后,我们可以重整列,将横线上方调换至原来的模样,而横线下方自然地会回到初始的样子。至此我们搞明白了为什么异或、同或自己就是自己的逆运算,而这就是刚刚我们说的直观上的交错感造成的。我们此时再从列与列之间看,这种交错的美感也更加强烈。
四、二进制数以外还能有类似的运算吗?
多进制——加密方式与运算规则的统一
布尔代数真是一个伟大的发明,我现在真是感觉知道得越多越无知,这异或 同或不过是冰山一角,简单的0与1背后还有看不到头的知识。
不过我们回顾这篇文章探索的过程,似乎常常在用枚举的方式研究问题,只是因为0和1的组合并不多,我们能够简单地分析罢了。
而这种枚举似乎并不是对运算的枚举,我们定义了运算 F1 F2等,而它们更像是一种数字与数字组合的对应方式。联系前文的密码概念,运算规则、加密方式似乎都能统一到数字与数字组合的对应方式上。
我们每天都用的加减法虽然是有现实依据的(物体个数的多少),但仍可以理解为是十进制的对应方式,只不过多了个进位的概念。
于是我畅想在多进制中说不定也能有像异或这样优美之物,可惜我才疏学浅且时间有限暂没有找到这样的东西,只能留给大佬来解答了。(大佬又怎么会翻到我的文章。。只能留给时间来解答了)
五、未解决之谜:如何在最初程序中把异或作为不进位加法理解?
实际上我最初知道逻辑门与布尔代数是在MC红石,为了玩红石数电学大佬整个CPU出来去自学了数字电路,可惜中途放弃了QAQ。
在加法器中,异或用来做不进位的加法,再用与门判断是否进位。最初看到这样异或实现交换的程序,震惊之余我想到的便是这一点,是否第二句异或也能够将进位的信息导出来之类的。虽然不进位加法只是异或的一个表象应用,而本质上是这篇文章的内容。但由本质及表象,把异或作为不进位加法理解数值交换又是否可行呢?再次等待大佬的解答~
- 其他常见逻辑运算能有高度对称性吗?——信息量上的前提
- 对称性的运算能继续推广吗?——密码加密与解码
- 异或的对称性具体是如何实现的?——二进制逻辑运算中唯一的光
- 二进制数以外还能有类似的运算吗?——编码方式与运算规则的统一
结语
我目前只不过是一位CS的大一小白,并没有接触过密码学信息论之类的东西,以上纯属我无聊时的胡思乱想,用词与内容可能都不是很严谨,欢迎各位指点补充。
这里十分感谢同学ZDJeffrey的一篇文章,他列举了C语言数值交换的可行实现,最后对异或与加减法关系的理解也是这篇文章的灵感来源:
从这里我得知了异或实现交换的方式,帮我消磨了工图课的时间 (bushi),最终整了这么篇文章出来。之后我还发现密码学中真的有异或加密以及奇偶校验等异或运算的相关内容,实在是欣喜若狂,高三刷题的洗礼之后真的很久没有像现在这样,凭自己搞出点有学术性甚至有点应用性的东西了。这一切还得归功于ZDJeffrey朋友圈的这篇文章呀。这位兄弟的文章也让我反思了自己写博客的思路不应当是简单记录想法给自己看,而是给别人以启发的。实在是感激不尽!