从汇编代码中探究整数的取余运算
前言
之前那一篇文章里面探讨了整数的除法运算,今天我们来再看一下整数的取余运算(modulo);取余运算中,取余的那个数(也就是%后面的那个数)正负号无所谓,所以下文都只谈取余数为正数的情况。
涉及取余数正负号的情况,只出现在当我们两个数的数据类型不一样的时候,比方说unsigned数据取余一个signed的数据,但本文不会谈及这种情况(因为没见过有人这么用)

一、n%1
n%1当然结果是0,就连我们没有优化的-O0都知道:
只不过在这里用的是mov这个指令,而xor会更快一点。

二、n取余2的平方
我们先假设n为一个unsigned类型的数据。
我们知道n%2只会有2种结果,要么是1(n为奇数)要么是0(n为偶数),我们的编译器自然也知道(准确的说是写编译器的人自然也知道),所以会生成以下的汇编代码:
这里是一个非常神奇的and运算(注意,这里的1是十进制下的1),这里的这个and相当于一个mask;我们只需要看最后一个bit,如果最后一位是1(奇数),那么我们就得到1,如果最后一位是0(偶数),那么我们就得到0
我们取余2的p次方时,我们只需要看最后的p个bit,比方说n%8,那么我们就看最后的3个bit,于是就有了:
接下来我们取消我们最初的那个假设,这个时候,n%2会给我们如下的代码:
这个代码突然变得非常复杂,因为我们需要考虑负数的情况。上面这个代码告诉我们,当我们只需要用到非负数取余的时候(比如说loop一个数组),我们最好指明我们的数据类型为unsigned
接下来,我们还是将上面的代码转化为公式:
还是和前文除法运算一样,我们见到了nSHR31这个值,直觉告诉我们这个是运用在负数的情况下的。我们在这里拿n=-1做一个例子,如果我们还是用最上面的那个and运算的话,我们最终得到的结果将会是
居然变成一个正数了??
但是如果我们用上述的公式的话,我们首先-1+1=0,然后0and1得到0,最后减去1得到-1。这里我们得到了一个正确的值-1。你还可以假设n为其他负数,然后自己算算看,

三、n取余其他整数
n取余其他整数的时候,实际上我们是先做一个除法运算,然后再做一个减法运算
比如说n%3我们会得到如下的代码
前四行我们已经在之前的除法运算一文中说过了——相当于n/3,后三行是
注意,在这里n/3不保留小数点,所以3*(n/3)不等同于n。
所以以上的公式就是我们的取余运算。

后记
写编译器的人果然都是数学大师,能够想得到常人所想不到的。
最后,如果你看上述代码有困难的话,可以参考我做的汇编语言101的视频。
参考工具:Compiler Explorer: https://godbolt.org/

THE END.