[英]How to code a modulo (%) operator in C/C++/Obj-C that handles negative numbers
我最討厭 C 派生語言(作為數學家)之一是
(-1) % 8 // comes out as -1, and not 7
fmodf(-1,8) // fails similarly
最好的解決辦法是什么?
C++ 允許模板和運算符重載的可能性,但這兩者對我來說都是渾水。 示例感謝收到。
首先,我想指出的是,您甚至不能依賴(-1) % 8 == -1
的事實。 您唯一可以依賴的是(x / y) * y + ( x % y) == x
。 然而,余數是否為負是實現定義的。
現在為什么要在這里使用模板? 整數和長整數的重載就可以了。
int mod (int a, int b)
{
int ret = a % b;
if(ret < 0)
ret+=b;
return ret;
}
現在你可以像 mod(-1,8) 一樣調用它,它看起來是 7。
編輯:我在我的代碼中發現了一個錯誤。 如果 b 是負數,它將不起作用。 所以我認為這是更好的:
int mod (int a, int b)
{
if(b < 0) //you can check for b == 0 separately and do what you want
return -mod(-a, -b);
int ret = a % b;
if(ret < 0)
ret+=b;
return ret;
}
參考:C++03第5.6段第4條:
二元 / 運算符產生商,二元 % 運算符產生第一個表達式除以第二個表達式的余數。 如果 / 或 % 的第二個操作數為零,則行為未定義; 否則 (a/b)*b + a%b 等於 a。 如果兩個操作數都為非負,則余數為非負; 如果不是,則余數的符號是 implementation-defined 。
這是一個 C 函數,它處理兩個操作數的正或負整數或小數值
#include <math.h>
float mod(float a, float N) {return a - N*floor(a/N);} //return in range [0, N)
從數學的角度來看,這無疑是最優雅的解決方案。 但是,我不確定它在處理整數方面是否穩健。 有時在轉換 int -> fp -> int 時會出現浮點錯誤。
我將此代碼用於非 int s,並為 int 使用單獨的函數。
注意:需要捕獲 N = 0!
測試代碼:
#include <math.h>
#include <stdio.h>
float mod(float a, float N)
{
float ret = a - N * floor (a / N);
printf("%f.1 mod %f.1 = %f.1 \n", a, N, ret);
return ret;
}
int main (char* argc, char** argv)
{
printf ("fmodf(-10.2, 2.0) = %f.1 == FAIL! \n\n", fmodf(-10.2, 2.0));
float x;
x = mod(10.2f, 2.0f);
x = mod(10.2f, -2.0f);
x = mod(-10.2f, 2.0f);
x = mod(-10.2f, -2.0f);
return 0;
}
(注意:您可以直接從 CodePad 編譯並運行它: http ://codepad.org/UOgEqAMA )
輸出:
fmodf(-10.2, 2.0) = -0.20 == 失敗!
10.2 模 2.0 = 0.2
10.2 模 -2.0 = -1.8
-10.2 模 2.0 = 1.8
-10.2 mod -2.0 = -0.2
我剛剛注意到 Bjarne Stroustrup 將%
標記為余數運算符,而不是模運算符。
我敢打賭,這是它在 ANSI C 和 C++ 規范中的正式名稱,而且術語的濫用已經悄然出現。有人知道這是事實嗎?
但是,如果是這種情況,那么 C 的 fmodf() 函數(可能還有其他函數)就非常具有誤導性。 它們應該被標記為 fremf() 等
找到正模數的最簡單的通用函數是這個 - 它適用於 x 的正值和負值。
int modulo(int x,int N){
return (x % N + N) %N;
}
對於整數,這很簡單。 就做
(((x < 0) ? ((x % N) + N) : x) % N)
我假設N
是正數並且可以在x
類型中表示。 你最喜歡的編譯器應該能夠優化它,這樣它就可以在匯編程序中完成一個 mod 操作。
這是一個舊問題的新答案,基於這篇Microsoft Research 論文和其中的參考資料。
請注意,從 C11 和 C++11 開始, div
的語義已變為向零截斷(參見[expr.mul]/4
)。 此外,對於D
除以d
,C++11 保證以下關於商qT
和余數rT
auto const qT = D / d;
auto const rT = D % d;
assert(D == d * qT + rT);
assert(abs(rT) < abs(d));
assert(signum(rT) == signum(D) || rT == 0);
其中signum
映射到 -1、0、+1,具體取決於其參數是否為 <、==、> 大於 0(請參閱此問答以獲取源代碼)。
對於截斷除法,余數的符號等於被除數D
的符號,即-1 % 8 == -1
。 C++11 還提供了一個std::div
函數,它根據截斷的除法返回一個帶有成員quot
和rem
的結構體。
還有其他可能的定義,例如所謂的地板除法可以根據內置的截斷除法來定義
auto const I = signum(rT) == -signum(d) ? 1 : 0;
auto const qF = qT - I;
auto const rF = rT + I * d;
assert(D == d * qF + rF);
assert(abs(rF) < abs(d));
assert(signum(rF) == signum(d));
使用地板除法時,余數的符號等於除數d
的符號。 在 Haskell 和 Oberon 等語言中,有用於地板除法的內置運算符。 在 C++ 中,您需要使用上述定義編寫一個函數。
另一種方法是歐幾里得除法,它也可以根據內置的截斷除法來定義
auto const I = rT >= 0 ? 0 : (d > 0 ? 1 : -1);
auto const qE = qT - I;
auto const rE = rT + I * d;
assert(D == d * qE + rE);
assert(abs(rE) < abs(d));
assert(signum(rE) >= 0);
使用歐幾里得除法,余數的符號總是非負的。
對於數學家來說,最好的解決方案是使用 Python。
C++ 運算符重載與此無關。 您不能重載內置類型的運算符。 你想要的只是一個函數。 當然,您可以使用 C++ 模板,通過 1 段代碼為所有相關類型實現該功能。
標准 C 庫為浮點類型提供fmod
,如果我沒fmod
名字的話。
對於整數,您可以定義一個 C++ 函數模板,該模板始終返回非負余數(對應於歐幾里德除法)為...
#include <stdlib.h> // abs
template< class Integer >
auto mod( Integer a, Integer b )
-> Integer
{
Integer const r = a%b;
return (r < 0? r + abs( b ) : r);
}
...並且只寫mod(a, b)
而不是a%b
。
這里Integer
類型需要是有符號整數類型。
如果您想要余數的符號與除數的符號相同的常見數學行為,那么您可以執行例如
template< class Integer >
auto floor_div( Integer const a, Integer const b )
-> Integer
{
bool const a_is_negative = (a < 0);
bool const b_is_negative = (b < 0);
bool const change_sign = (a_is_negative != b_is_negative);
Integer const abs_b = abs( b );
Integer const abs_a_plus = abs( a ) + (change_sign? abs_b - 1 : 0);
Integer const quot = abs_a_plus / abs_b;
return (change_sign? -quot : quot);
}
template< class Integer >
auto floor_mod( Integer const a, Integer const b )
-> Integer
{ return a - b*floor_div( a, b ); }
...對Integer
具有相同的約束,即它是有符號類型。
¹ 因為 Python 的整數除法向負無窮大舍入。
我相信這個問題的另一個解決方案是使用 long 類型的變量而不是 int 類型的變量。
我只是在處理一些代碼,其中 % 運算符返回了一個負值,這導致了一些問題(為了在 [0,1] 上生成統一的隨機變量,你並不真正想要負數 :) ),但是在將變量切換到輸入 long,一切運行順利,結果與我在 python 中運行相同代碼時得到的結果相匹配(對我來說很重要,因為我希望能夠在多個平台上生成相同的“隨機”數字。
哦,我也討厭這種設計......
您可以通過以下方式將股息轉換為無符號:
unsigned int offset = (-INT_MIN) - (-INT_MIN)%divider
result = (offset + dividend) % divider
其中 offset 最接近 (-INT_MIN) 模數的倍數,因此加減它不會改變模數。 請注意,它具有無符號類型,結果將為整數。 不幸的是,它無法正確轉換值 INT_MIN...(-offset-1),因為它們會導致 arifmetic 溢出。 但是這種方法在使用常量除法器時每個操作只有一個附加算術(並且沒有條件)的優點,因此它可用於類似 DSP 的應用程序。
有一種特殊情況,其中除法器為 2 N (2 的整數冪),可以使用簡單的算術和按位邏輯計算模數為
dividend&(divider-1)
例如
x mod 2 = x & 1
x mod 4 = x & 3
x mod 8 = x & 7
x mod 16 = x & 15
更常見且不太棘手的方法是使用此函數求模(僅適用於正分頻器):
int mod(int x, int y) {
int r = x%y;
return r<0?r+y:r;
}
如果它是負數,這只是正確的結果。
你也可以欺騙:
(p%q + q)%q
它很短,但使用兩個通常很慢的 %-s。
對於不使用分支且僅使用 1 個 mod 的解決方案,您可以執行以下操作
// Works for other sizes too,
// assuming you change 63 to the appropriate value
int64_t mod(int64_t x, int64_t div) {
return (x % div) + (((x >> 63) ^ (div >> 63)) & div);
}
/* Warning: macro mod evaluates its arguments' side effects multiple times. */ #define mod(r,m) (((r) % (m)) + ((r)<0)?(m):0)
...或者只是習慣於獲得等價類的任何代表。
C++ 的示例模板
template< class T >
T mod( T a, T b )
{
T const r = a%b;
return ((r!=0)&&((r^b)<0) ? r + b : r);
}
使用此模板,返回的余數將為零或與除數(分母)具有相同的符號(相當於向負無窮大舍入),而不是余數為零或與被除數具有相同符號的 C++ 行為(分子)(相當於向零舍入)。
define MOD(a, b) ((((a)%(b))+(b))%(b))
unsigned mod(int a, unsigned b) {
return (a >= 0 ? a % b : b - (-a) % b);
}
我會這樣做:
((-1)+8) % 8
在根據需要進行模數給出 7 之前,這會將后一個數字添加到第一個數字上。 這應該適用於低至 -8 的任何數字。 對於 -9 添加 2*8。
此解決方案(用於當mod
為正時)避免同時進行負除法或余數運算:
int core_modulus(int val, int mod)
{
if(val>=0)
return val % mod;
else
return val + mod * ((mod - val - 1)/mod);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.