[英]Python-style integer division & modulus in C
在Python和Ruby中,帶符號的整數除法向負無窮大截斷,有符號整數模數與第二個操作數具有相同的符號:
>>> (-41) / 3
-14
>>> (-41) % 3
1
但是,在C和Java中,帶符號的整數除法截斷為0,有符號整數模數與第一個操作數的符號相同:
printf("%d\n", (-41) / 3); /* prints "-13" */
printf("%d\n", (-41) % 3); /* prints "-2" */
在C和Python中執行相同類型的除法和模數的最簡單,最有效的方法是什么?
舊的C標准中未指定使用有符號整數除法進行舍入的方向。 但是,在C99中,它被指定為向零舍入。
這里的可移植代碼適用於所有版本的C標准和CPU架構:
int py_div(int a, int b)
{
if (a < 0)
if (b < 0)
return -a / -b;
else
return -(-a / b) - (-a % b != 0 ? 1 : 0);
else if (b < 0)
return -(a / -b) - (a % -b != 0 ? 1 : 0);
else
return a / b;
}
int py_mod(int a, int b)
{
if (a < 0)
if (b < 0)
return -(-a % -b);
else
return -a % b - (-a % -b != 0 ? 1 : 0);
else if (b < 0)
return -(a % -b) + (-a % -b != 0 ? 1 : 0);
else
return a % b;
}
我做了一些表面測試,它似乎給出了與Python相同的結果。 這段代碼可能不是最有效的,但是一個好的C編譯器可能可以充分地優化它,特別是如果你把代碼作為靜態函數放在頭中。
您可能還想看一下這個密切相關的問題: 整數除法在C ++中用負數舍入 。
對於模數,我發現以下最簡單。 實現的符號約定無關緊要,我們只是將結果強制轉換為我們想要的符號:
r = n % a;
if (r < 0) r += a;
顯然這是積極的。 對於負面的你需要:
r = n % a;
if (r > 0) r += a;
哪個(可能有點令人困惑)結合起來給出了以下內容(在C ++中。在C中用int執行相同的操作,然后繁瑣地寫一個副本很長時間):
template<typename T> T sign(T t) { return t > T(0) ? T(1) : T(-1); }
template<typename T> T py_mod(T n, T a) {
T r = n % a;
if (r * sign(a) < T(0)) r += a;
return r;
}
我們可以使用cheapskate二值“符號”函數,因為我們已經知道了!= 0,或者%將是未定義的。
將相同的原理應用於除法(查看輸出而不是輸入):
q = n / a;
// assuming round-toward-zero
if ((q < 0) && (q * a != n)) --q;
可以說,乘法可能比必要的更昂貴,但如果需要,可以在每個架構的基礎上進行微優化。 例如,如果你有一個為你提供商和余數的除法運算,那么你就會對除法進行排序。
[編輯:可能存在一些出現問題的邊緣情況,例如,如果商或余數是INT_MAX或INT_MIN。 但是,為大值模擬python數學無論如何都是另一個問題;-)]
[另一個編輯:不是用C編寫的標准python實現嗎? 你可以搜索他們所做的事情的來源]
這是C89中的分層和模數的簡單實現:
#include <stdlib.h>
div_t div_floor(int x, int y)
{
div_t r = div(x, y);
if (r.rem && (x < 0) != (y < 0)) {
r.quot -= 1;
r.rem += y;
}
return r;
}
這里使用div
是因為它具有明確定義的行為 。
如果你正在使用C ++ 11,這里是一個模板化的分區和模數的實現:
#include <tuple>
template<class Integral>
std::tuple<Integral, Integral> div_floor(Integral x, Integral y)
{
typedef std::tuple<Integral, Integral> result_type;
const Integral quot = x / y;
const Integral rem = x % y;
if (rem && (x < 0) != (y < 0))
return result_type(quot - 1, rem + y);
return result_type(quot, rem);
}
在C99和C ++ 11中,您可以避免使用div
因為C中除法和模數的行為不再依賴於實現。
這個問題的解決方案比已經提出的解決方案短得多(在代碼中)。 我將使用Ville Laurikari的答案格式:
int py_div(int a, int b)
{
return (a - (((a % b) + b) % b)) / b);
}
int py_mod(int a, int b)
{
return ((a % b) + b) % b;
}
不幸的是,似乎上述解決方案表現不佳。 在對Ville Laurikari的解決方案進行基准測試時,很明顯這個解決方案的執行速度只有一半。
經驗教訓是:雖然分支指令使代碼變慢,但除法指令更糟糕!
我認為如果只是因為它的優雅,我發布了這個解決方案。
問題是關於如何模擬Python樣式的整數除法和模數。 這里給出的所有答案都假定此操作的操作數本身是整數,但Python也可以使用浮點數進行模運算。 因此,我認為以下答案可以更好地解決問題:
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
int pydiv(double a, double b) {
int q = a/b;
double r = fmod(a,b);
if ((r != 0) && ((r < 0) != (b < 0))) {
q -= 1;
}
return q;
}
int main(int argc, char* argv[])
{
double a = atof(argv[1]);
double b = atof(argv[2]);
printf("%d\n", pydiv(a, b));
}
對於模數:
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
double pymod(double a, double b) {
double r = fmod(a, b);
if (r!=0 && ((r<0) != (b<0))) {
r += b;
}
return r;
}
int main(int argc, char* argv[])
{
double a = atof(argv[1]);
double b = atof(argv[2]);
printf("%f\n", pymod(a, b));
}
我使用以下測試代碼測試了上述兩個程序與Python的行為方式:
#!/usr/bin/python3
import subprocess
subprocess.call(["cc", "pydiv.c", "-lm", "-o", "cdiv"])
subprocess.call(["cc", "pymod.c", "-lm", "-o", "cmod"])
def frange(start, stop, step=1):
for i in range(0, int((stop-start)/step)):
yield start + step*i
for a in frange(-10.0, 10.0, 0.25):
for b in frange(-10.0, 10.0, 0.25):
if (b == 0.0):
continue
pydiv = a//b
pymod = a%b
cdiv = int(subprocess.check_output(["./cdiv", str(a), str(b)]))
cmod = float(subprocess.check_output(["./cmod", str(a), str(b)]))
if pydiv != cdiv:
exit(1)
if pymod != cmod:
exit(1)
以上將比較Python division和modulo的行為與我在6320測試用例中提供的C實現。 由於比較成功,我相信我的解決方案正確地實現了Python各自操作的行為。
它深入研究浮動的丑陋世界,但這些在Java中給出了正確的答案:
public static int pythonDiv(int a, int b) {
if (!((a < 0) ^ (b < 0))) {
return a / b;
}
return (int)(Math.floor((double)a/(double)b));
}
public static int pythonMod(int a, int b) {
return a - b * pythonDiv(a,b);
}
我對他們的效率沒有斷言。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.