複製鏈接
請複製以下鏈接發送給好友

取模運算

鎖定
取模運算是求兩個數相除的餘數 [1] 
取模運算(Modulo Operation)和取餘運算(Remainder Operation)兩個概念有重疊的部分但又不完全一致。主要的區別在於對負整數進行除法運算時操作不同。取模主要是用於計算機術語中。取餘則更多是數學概念
模運算在數論和程序設計中都有着廣泛的應用,奇偶數的判別到素數的判別,從模冪運算到最大公約數的求法,從孫子問題到凱撒密碼問題,無不充斥着模運算的身影。雖然很多數論教材上對模運算都有一定的介紹,但多數都是以純理論為主,對於模運算在程序設計中的應用涉及不多。
中文名
取模運算
公    式
n = kp + r
求整數商
c = [a/b]
計算模
r = a - c*b

取模運算區別於取餘運算

對於整型數a,b來説,取模運算或者求餘運算的方法都是:
1.求整數商: c = [a/b];
2.計算模或者餘數: r = a - c*b.
求模運算和求餘運算在第一步不同: 取餘運算在取c的值時,向0 方向舍入(fix()函數);而取模運算在計算c的值時,向負無窮方向舍入(floor()函數)。
例1.計算:-7 Mod 4
那麼:a = -7;b = 4;
第一步:求整數商c:
①進行求模運算時:c = [a/b] = -7 / 4 = -2(向負無窮方向舍入),
②進行求餘運算時:c = [a/b] = -7 / 4 = -1(向0方向舍入);
第二步:計算模和餘數的公式相同,但因c的值不同,
①求模時:r = a - c*b =-7 - (-2)*4 = 1,
②求餘時:r = a - c*b = -7 - (-1)*4 =-3。
例2.計算:7 Mod 4
那麼:a = 7;b = 4
第一步:求整數商c:
①進行求模運算c = [a/b] = 7 / 4 = 1
②進行求餘運算c = [a/b] = 7 / 4 = 1
第二步:計算模和餘數的公式相同
①求模時:r = a - c*b =7 - (1)*4 = 3,
②求餘時:r = a - c*b = 7 - (1)*4 =3。
歸納:當a和b正負號一致時,求模運算和求餘運算所得的c的值一致,因此結果一致。
正負號不一致時,結果不一樣。
另外各個環境下%運算符的含義不同,比如c/c++,java 為取餘,而python則為取模。
補充:
7 mod 4 = 3(商 = 1 或 2,1<2,取商=1)
-7 mod 4 = 1(商 = -1 或 -2,-2<-1,取商=-2)
7 mod -4 = -1(商 = -1或-2,-2<-1,取商=-2)
-7 mod -4 = -3(商 = 1或2,1<2,取商=1)
這裏模是4,取模其實全稱應該是取模數的餘數,或取模餘。
增加補充內容(以上五行)後,被修改商值,但是括號內容不變,出現奇怪矛盾。
在python下 % 運算符代表取模,如要修改,請先用python做
-7 % 4
運算,或其它語言做取模運算驗證,理解後再動手。

取模運算概念

取模運算定義

給定一個正整數p,任意一個整數n,一定存在等式
n = kp + r ;
其中 k、r 是整數,且 0 ≤ r < p,則稱 k 為 n 除以 p 的商,r 為 n 除以 p 的餘數。
對於正整數 p 和整數 a,b,定義如下運算:
取模運算:a % p(或a mod p),表示a除以p的餘數。
模p加法: ,其結果是a+b算術和除以p的餘數。
模p減法: ,其結果是a-b算術差除以p的餘數。
模p乘法: ,其結果是 a * b算術乘法除以p的餘數。
説明:
1. 同餘式:正整數a,b對p取模,它們的餘數相同,記做 或者a ≡ b (mod p)。
2. n % p 得到結果的正負由被除數n決定,與p無關。例如:7%4 = 3, -7%4 = -3, 7%-4 = 3, -7%-4 = -3。

取模運算基本性質

  1. 若p|(a-b),則a≡b (% p)。例如 11 ≡ 4 (% 7), 18 ≡ 4(% 7)
  2. (a % p)=(b % p)意味a≡b (% p)
  3. 對稱性:a≡b (% p)等價於b≡a (% p)
  4. 傳遞性:若a≡b (% p)且b≡c (% p) ,則a≡c (% p)

取模運算運算規則

模運算與基本四則運算有些相似,但是除法例外。其規則如下:
  1. (a + b) % p = (a % p + b % p) % p (1)
  2. (a - b) % p = (a % p - b % p ) % p (2)
  3. (a * b) % p = (a % p * b % p) % p (3)
  4. a ^ b % p = ((a % p)^b) % p (4)
  • 結合律:((a+b) % p + c) % p = (a + (b+c) % p) % p (5)
((a*b) % p * c)% p = (a * (b*c) % p) % p (6)
  • 交換律:(a + b) % p = (b+a) % p (7)
(a * b) % p = (b * a) % p (8)
  • 分配律:(a+b) % p = ( a % p + b % p ) %p(9)
  • ((a +b)% p * c) % p = ((a * c) % p + (b * c) % p) % p (10)

取模運算重要定理

  • 若a≡b (% p),則對於任意的c,都有(a + c)/ ≡ (b + c) (%p);(11)
  • 若a≡b (% p),則對於任意的c,都有(a * c) ≡ (b * c) (%p);(12)
  • 若a≡b (% p),c≡d (% p),則 (a + c) ≡ (b + d) (%p),(a - c) ≡ (b - d) (%p),(a * c) ≡ (b * d) (%p); (13)

取模運算應用

判別奇偶數
奇偶數的判別是模運算最基本的應用,也非常簡單。
已知一個整數n對2取模,如果餘數為0,則表示n為偶數,否則n為奇數。
C++實現功能函數:
/*
函數名:IsEven
函數功能:判別整數n的奇偶性。能被2整除為偶數,否則為奇數
輸入值:intn,整數n
返回值:bool,若整數n是偶數,返回true,否則返回false
*/
bool IsEven(int n)
{
    return(n%2==0);
}

判別素數
一個數,如果只有1和它本身兩個因數,這樣的數叫做質數(或素數)。例如 2,3,5,7 是質數,而 4,6,8,9 則不是,後者稱為合成數或合數
判斷某個自然數是否是素數最常用的方法就是試除法——用不比該自然數的平方根大的正整數去除這個自然數,若該自然數能被整除,則説明其非素數。
C++實現功能函數:
/*函數名:IsPrime函數功能:判別自然數n是否為素數。輸入值:intn,自然數n返回值:bool,若自然數n是素數,返回true,否則返回false*/
bool IsPrime(unsigned int n)
{
    unsigned maxFactor=sqrt(n);//n的最大因子
    for(unsigned int i=2;i<=maxFactor;i++)
    {
        if(n%i==0)//n能被i整除,則説明n非素數
        {
            return false;
        }
    }
    return true;
}
求最大公約數
求最大公約數最常見的方法是歐幾里德算法(又稱輾轉相除法),其計算原理依賴於定理:gcd(a,b) = gcd(b,a mod b)
證明:
a可以表示成a = kb + r,則r = a mod b
假設d是a,b的一個公約數,則有d|a, d|b,而r = a - kb,因此d|r
因此d是(b,a mod b)的公約數
假設d 是(b,a mod b)的公約數,則d | b , d |r ,但是a = kb +r
因此d也是(a,b)的公約數
因此(a,b)和(b,a mod b)的公約數是一樣的,其最大公約數也必然相等,得證。
C++實現功能函數:
/*函數功能:利用歐幾里德算法,採用遞歸方式,求兩個自然數的最大公約數函數名:Gcd輸入值:unsigned int a,自然數a;unsigned int b,自然數b返回值:unsigned int,兩個自然數的最大公約數*/
unsigned int Gcd(unsigned int a,unsigned int b)
{
    if(b==0)
    return a;
    return Gcd(b,a%b);
}
/*函數功能:利用歐幾里德算法,採用迭代方式,求兩個自然數的最大公約數函數名:Gcd輸入值:unsigned int a,自然數a;unsigned int b,自然數b返回值:unsigned int,兩個自然數的最大公約數*/
unsigned int Gcd(unsigned int a,unsigned int b)
{
    unsigned int temp;
    while(b!=0)
    {
        temp=a%b;
        a=b;
        b=temp;
    }
    return a;
}
水仙花數是指一個 n 位正整數 ( n≥3 ),它的每個位上的數字的 n 次冪之和等於它本身。(例如:1^3 + 5^3+ 3^3 = 153)。
水仙花數只是自冪數的一種,嚴格來説三位數的3次冪數才稱為水仙花數。
附:其他位數的自冪數名字
一位自冪數:獨身數
兩位自冪數:沒有
三位自冪數:水仙花數
四位自冪數:四葉玫瑰數
五位自冪數:五角星數
六位自冪數:六合數
七位自冪數:北斗七星數
八位自冪數:八仙數
九位自冪數:九九重陽數
十位自冪數:十全十美數
假設:取1至1000內的水仙花數,那麼其實只有當i>99時才成立,因為水仙花數是由3位數組成。
如果要判斷一個三位數是否為水仙花數
根據運算規則,水仙花數是三位數的每個位的數的3次冪,例如999,需要取9,9,9三個數並且三數相乘的合再判斷。
程序循環方式:
需要用取餘數的整數的方式去完成判斷條件:分別從三位數中利用取餘去取百位、十位、個位數,加以判斷
var a,b,c,d
for(i=1;i<1000;i++){
a = parseInt(i%10); //這一步取到了個位數
b = parseInt(i/10%10); //這一步取到了十位數
c= parseInt(i/100%10); //這一步取到了百位數
d = a*a*a+b*b*b+c*c*c;//水仙花數
if(d==i&&d>99){//比較判斷,且是三位數。
alert(d+"是水仙花數") //輸出水仙花數。
}
}
模冪運算
利用模運算的運算規則,我們可以使某些計算得到簡化。
例如,我們想知道3333^5555的末位是什麼。很明顯不可能直接把3333^5555的結果計算出來,那樣太大了。但我們想要確定的是3333^5555(%10),所以問題就簡化了。
根據運算規則(4)a^b % p = ((a % p)^b) % p ,我們知道3333^5555(%10)= 3^5555(%10)。
根據運算規則(3) (a * b) % p = (a % p * b % p) % p ,由於5555 = 4 * 1388 + 3,我們得到3^5555(%10)=(3^(4*1388) * 3^3)(%10)=((3^(4*1388)(%10)* 3^3(%10))(%10)
=((3^(4*1388)(%10)* 7)(%10)。
根據歐拉定理可以得到 3 ^ (4 * k) % 10 = 1, 所以((3^(4*1388)(%10)* 7)(%10)= (1 * 7) (% 10) = 7
計算完畢。
利用這些規則我們可以有效地計算X^N(% P)。簡單的算法是將result初始化為1,然後重複將result乘以X,每次乘法之後應用%運算符(這樣使得result的值變小,以免溢出),執行N次相乘後,result就是我們要找的答案。
這樣對於較小的N值來説,實現是合理的,但是當N的值很大時,需要計算很長時間,是不切實際的。下面的結論可以得到一種更好的算法。
如果N是偶數,那麼X^N =(X*X)^[N/2];
如果N是奇數,那麼X^N = X*X^(N-1) = X *(X*X)^[N/2];
其中[N]是指小於或等於N的最大整數。
C++實現功能函數:
/*函數功能:利用模運算規則,採用遞歸方式,計算X^N(%P)函數名:PowerMod輸入值:unsigned int x,底數x unsigned int n,指數nunsigned int p,模p返回值:unsigned int,X^N(%P)的結果*/
unsignedintPowerMod(unsignedintx,unsignedintn,unsignedintp)
{
if(n==0)
{
return1;
}
unsignedinttemp=PowerMod((x*x)%p,n/2,p);//遞歸計算(X*X)^[N/2]
if((n&1)!=0)//判斷n的奇偶性
{
temp=(temp*x)%p;
}
returntemp;
}
《孫子問題(中國剩餘定理)》
在我國古代算書《孫子算經》中有這樣一個問題:
“今有物不知其數,三三數之剩二,五五數之剩三,七七數之剩二,問物幾何?”意思是,“一個數除以3餘2,除以5餘3,除以7餘2.求適合這個條件的最小數。”
這個問題稱為“孫子問題”.關於孫子問題的一般解法,國際上稱為“中國剩餘定理”.
我國古代學者早就研究過這個問題。例如我國明朝數學家程大位在他著的《算法統宗》(1593年)中就用四句很通俗的口訣暗示了此題的解法:
三人同行七十稀,五樹梅花廿一枝,七子團圓正半月,除百零五便得知。
"正半月"暗指15。"除百零五"的原意是,當所得的數比105大時,就105、105地往下減,使之小於105;這相當於用105去除,求出餘數。
這四句口訣暗示的意思是:當除數分別是3、5、7時,用70乘以用3除的餘數,用21乘以用5除的餘數,用15乘以用7除的餘數,然後把這三個乘積相加。加得的結果如果比105大,就除以105,所得的餘數就是滿足題目要求的最小正整數解。
根據剩餘定理,我們把此種解法推廣到有n(n為自然數)個除數對應n個餘數,求最小被除數的情況。輸入n個除數(除數不能互相整除)和對應的餘數,計算機將輸出最小被除數。
C++實現功能函數:
/*函數名:ResidueTheorem函數功能:運用剩餘定理,解決推廣了的孫子問題。通過給定n個除數(除數不能互相整除)和對應的餘數,返回最小被除數輸入值:unsignedintdevisor[],存儲了n個除數的數組unsignedintremainder[],存儲了n個餘數的數組intlength,數組的長度返回值:unsignedint,最小被除數*/
unsignedintResidueTheorem(constunsignedintdevisor[],constunsignedintremainder[],intlength)
{
unsignedintproduct=1;//所有除數之乘積
for(inti=0;i<length;i++)//計算所有除數之乘積
{
product*=devisor[i];
}//公倍數數組,表示除該元素(除數)之外其他除數的公倍數
unsignedint*commonMultiple=newunsignedint(length);
for(inti=0;i<length;i++)//計算除該元素(除數)之外其他除數的公倍數
{
commonMultiple[i]=product/devisor[i];
}
unsignedintdividend=0;//被除數,就是函數要返回的值
for(inti=0;i<length;i++)//計算被除數,但此時得到的不是最小被除數
{
unsignedinttempMul=commonMultiple[i];//按照剩餘理論計算合適的公倍數,使得tempMul%devisor[i]==1
while(tempMul%devisor[i]!=1)
{
tempMul+=commonMultiple[i];
}
dividend+=tempMul*remainder[i];//用本除數得到的餘數乘以其他除數的公倍數
}
delete[]commonMultiple;return(dividend%product);//返回最小被除數
}
凱撒密碼(caeser)是羅馬擴張時期朱利斯·凱撒(Julius Caesar)創造的,用於加密通過信使傳遞的作戰命令
它將字母表中的字母移動一定位置而實現加密。注意26個字母循環使用,z的後面可以看成是a。
例如,當密匙為k = 3,即向後移動3位時,若明文為”How are you!”,則密文為”Krz duh btx!”。
凱撒密碼的加密算法極其簡單。其加密過程如下:
在這裏,我們做此約定:明文記為m,密文記為c,加密變換記為E(key1,m)(其中key1為密鑰),
解密變換記為D(key2,m)(key2為解密密鑰)(在這裏key1=key2,不妨記為key)。
凱撒密碼的加密過程可記為如下一個變換:c≡m+key (mod n) (其中n為基本字符個數)
同樣,解密過程可表示為:m≡c+key (mod n) (其中n為基本字符個數)
C++實現功能函數:
/*函數功能:使用凱撒密碼原理,對明文進行加密,返回密文函數名:Encrypt輸入值:constcharproclaimedInWriting[],存儲了明文的字符串charcryptograph[],用來存儲密文的字符串intkeyey,加密密匙,正數表示後移,負數表示前移返回值:無返回值,但是要將新的密文字符串返回*/


voidEncrypt(constcharproclaimedInWriting[],charcryptograph[],intkey)


{


constintNUM=26;//字母個數


intlen=strlen(proclaimedInWriting);


for(inti=0;i<len;i++)


{


if(proclaimedInWriting[i]>='a'&&proclaimedInWriting[i]<='z')
 
{
  cryptograph[i]=(proclaimedInWriting[i]-'a'+key)%NUM+'a';//明碼是大寫字母,則密碼也為大寫字母
 }


else if(proclaimedInWriting[i]>='A'&&proclaimedInWriting[i]<='Z')
 {

cryptograph[i]=(proclaimedInWriting[i]-'A'+key)%NUM+'A';//明碼是小寫字母,則密碼也為小寫字母

}


else{cryptograph[i]=proclaimedInWriting[i];//明碼不是字母,則密碼與明碼相同}


}


cryptograph[len]='\0';
}
/*函數功能:使用凱撒密碼原理,對密文進行解密,返回明文函數名:Decode輸入值:charproclaimedInWriting[],用來存儲明文的字符串constcharcryptograph[],存儲了密文的字符串intkeyey,解密密匙,正數表示前移,負數表示後移(與加密相反)返回值:無返回值,但是要將新的明文字符串返回*/
voidDecode(constcharcryptograph[],charproclaimedInWriting[],intkey)
{
 constintNUM=26;//字母個數
 intlen=strlen(cryptograph);
 for(inti=0;i<len;i++)
 {
  if(cryptograph[i]>='a'&&cryptograph[i]<='z')
 {
   NUMproclaimedInWriting[i]=(cryptograph[i]-'a'-key+NUM)%NUM+'a';//密碼是大寫字母,則明碼也為大寫字母,為防止出現負數,轉換時要加個
 }
  else if(cryptograph[i]>='A'&&cryptograph[i]<='Z')
  {
  proclaimedInWriting[i]=(cryptograph[i]-'A'-key+NUM)%NUM+'A';//密碼是小寫字母,則明碼也為小寫字母
  }
  else{//密碼不是字母,則明碼與明密相同proclaimedInWriting[i]=cryptograph[i];}
}
proclaimedInWriting[len]='\0';
}
模運算及其簡單應用就先講到這了,其實模運算在數學及計算機領域的應用非常廣泛。
參考資料
  • 1.    王法波編著. 從零開始學Java[M]. 北京:中國鐵道出版社, 2010.12.39頁