簡體   English   中英

如何在處理負數的 C/C++/Obj-C 中編寫模 (%) 運算符

[英]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函數,它根據截斷的除法返回一個帶有成員quotrem的結構體。

還有其他可能的定義,例如所謂的地板除法可以根據內置的截斷除法來定義

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM