取模和求余运算
文章目錄
- 背景
- 探究
- 總結
被除數 dividend 用 a 表示;
除數 divisor 用 b 表示;
商 quotient 用 q 表示;
余 remainder 用 rem 表示;
模 modulo 用 mod 表示。
背景
最近在一道 Java 習題中,看到這樣的一道題:
What is the output when this statement executed: System.out.printf(-7 % 3);正整數的取余運算大家都很熟悉,但是對于負數、實數的取余運算,確實給人很新鮮的感覺。于是我對此進行了一些探索。我發現,這里面還是頗有一點可以探索的東西的。
探究
首先,看看自然數的取模運算(定義1):
如果 a 和 b 是兩個自然數,b 非零,可以證明存在兩個整數 q 和 r,滿足 a = q*b + r 且0 ≤ r < b。其中,q 被稱為商,r 被稱為余數。
我們計算下 (-7) % 3,這個表達式正常情況下是求余數,計算過程如下圖所示:
按自然數的除法運算規則,得到商值:-3,余數:2,且完全滿足上述兩個關系表達式:-7 = (-3)*3 + 2 且 0 ≤ 2 < 3。
那么,各種編程語言和計算器是否是按照這樣的規則計算呢?下面列舉了幾個程序運算的結果:
| C++(G++ 編譯) | cout << (-7) % 3; | -1 |
| Java(1.6) | System.out.println((-7) % 3); | -1 |
| Python 2.6 | (-7) % 3 | 2 |
| 百度計算器 | (-7) mod 3 | 2 |
| Google 計算器 | (-7) mod 3 | 2 |
可以看到,結果特別有意思。這個問題是百家爭鳴的。看來我們不能直接把自然數的法則用在負數上。實際上,在整數范圍內,自然數的求余法則并不被很多人所接受,大家大多認可的是下面的這個(定義 2):
如果 a 和 b 是兩個自然數,b 非零,可以證明存在兩個整數 q 和 r,滿足 a = q*b + r 且0 ≤ |r| < |b|。其中,q 被稱為商,r 被稱為余數。
可以看到,上述定義導致了有負數的求余并不是我們想象的那么簡單,根據上述的定義,我們計算下 (-7) % 3,計算過程如下圖所示:
注:按自然數的除法運算規則(定義 1),商和除數的乘積要小于等于被減數才行,但是很多程序并沒有遵循定義 1的運算規則,所以依據定義 2的規則,商和除數的乘積可以大于被減數,使用上圖所示的技巧計算時,商與除數的乘積必須接近被減數,然后計算得到結果,再驗證定義 2的兩個表達式是否成立。當然了如果你直接使用定義 2的兩個表達式硬套出 p 和 r 的值也可以,不過不推薦。
如上圖所示可以得到兩組結果:
這兩組結果都滿足“定義 2”的表達式,也就是說 2 和 -1 都是 (-7) % 3 正確的結果, 所以問題來了到底余數是 2 還是 -1 呢?這個問題最后會給出答案。
我們把 2 和 -1 分別叫做正余數和負余數。通常,整數 a 除以整數 b 時,如果得到正余數為 r1,負余數為 r2,那么存在這樣的關系:r1 = r2 + b。
看完了 (-7) % 3,下面我們來看一看 7 % (-3) 的情況。根據定義 2,計算過程如下圖所示:
如上圖所示,可以得到兩組結果:
| C++(G++ 編譯) | cout << 7 % (-3); | 1 |
| Java(1.6) | System.out.println(7 % (-3)); | 1 |
| Python 2.6 | 7 % (-3) | -2 |
| 百度計算器 | 7 mod (-3) | -2 |
| Google 計算器 | 7 mod (-3) | -2 |
從中我們看到幾個很有意思的現象:
Java 緊隨 C++ 的步伐,而 Python、Google、百度步調一致。難道真是物以類聚?聯想一下,Google 一直支持 Python,Python 也頗有 Web 特色的感覺,而且 Google Application Engine 也用的 Python,國內的搜索引擎也不約而同地按照 Google 的定義進行運算。
可以推斷,C++ 和 Java 通常會盡量讓商更大一些。比如在 (-7) mod 3中,他們以 -2 為商,余數為 -1。在 Python 和 Google 計算器中,盡量讓商更小,所以以 -3 為商。在 7 mod (-3) 中效果相同:C++ 選擇了 3 作為商,Python 選擇了 2 作為商。但是在正整數運算中,所有語言和計算器都遵循了盡量讓商小的原則,因此 7 mod 3 結果為 1 不存在爭議,不會有人說它的余數是 -2。
上述的認知其實是錯誤的,糾正如下:
Java 和 C++ 的 % 是求余運算符,求余遵循讓商向 0 靠近的原則(即商向 0 方向舍入取整),也就是說求余是取 q 更趨近 0 時的 r,所以 Java 和 C++ 計算 7 % (-3) 的結果是 1,因為商 -2 更靠近 0;而 Python 的 % 是取模運算符,取模遵循讓商向負無窮靠近的原則(商向無窮小方向舍入取整,向負無窮方向舍入取整),也就是說取模是取 q 更趨近無窮小(負無窮)時的 r,所以 Python、百度、谷歌輸出的結果是 -2,因為 -3 更靠近負無窮的方向。
如果按照第二點的推斷,我測試一下 (-7) % (-3),結果應該是前一組語言(C++,Java)返回 2,后一組返回 -1。(請注意這只是假設)
于是我做了實際測試:
| C++(G++ 編譯) | cout << -7 % (-3); | -1 |
| Java(1.6) | System.out.println(-7 % (-3)); | -1 |
| Python 2.6 | -7 % (-3) | -1 |
| 百度計算器 | -7 mod (-3) | -1 |
| Google 計算器 | -7 mod (-3) | -1 |
結果讓人大跌眼鏡,所有語言和計算機返回結果完全一致。
這個眼鏡也白跌了,上述結果完全符合求余時商向 0 靠近的原則,取模時商向負無窮方向靠近的原則,所以都輸出 -1 根本就在預料之內。
我們看下 (-7) % (-3) 的運算過程,如下圖所示:
所以根據求余時商向 0 靠近的原則,取模時商向負無窮方向靠近的原則,求余和取模的商都選擇 2,所以余數和模數都是 -1,結果輸出的都是 -1。
總結
我們由此可以總結出下面兩個結論:
這個總結也是錯誤的。
最后總結:除法運算遵循定義 2的規則,求余是取 q 更趨近 0 時的 r,取模是取 q 更趨近負無窮的 r,如果被除數和除數符號相同,因為取相同的商值,所以余數和模數相同,被除數和除數的符號不同,則余數和模數會不同。
總結
- 上一篇: 上海小型餐饮怎么备案营业(上海小型餐饮怎
- 下一篇: 求余和取模的计算公式