Negatif sayıları işleyen C / C ++ / Obj-C modulo (%) operatörü nasıl kodlanır


87

C türevi dillerden (bir matematikçi olarak) nefret ettiğim şeylerden biri,

(-1) % 8 // comes out as -1, and not 7

fmodf(-1,8) // fails similarly

En iyi çözüm nedir?

C ++ şablonların ve operatörün aşırı yüklenmesi olasılığına izin veriyor, ancak bunların ikisi de benim için bulanık sular. örnekler minnetle alındı.


1
Bunun resmi tanıma göre stackoverflow.com/questions/828092/… ' nin tam bir "kopyası" olduğunu sanmıyorum . Bu sorunun yanıtlarının bu sorunun yanıtlarıyla birleştirilebileceği doğru değil, çünkü bu soru bölmeyi değil, yalnızca modülü soruyor. Ama bence bu soru bununla kaplıdır, bu yüzden yakın. Cevabım zaten orada, FWIW.
Steve Jessop

Belki de bu konu iki ayrı soru sorduğu için bölünmelidir. bunu yapmanın en iyi yolu, bölme sorusunu ayrı ayrı sormak ve ardından bu yanıta yönlendirmek olabilir. Bunu bu web sitesinin mekanizmalarını daha iyi anlayan birine bırakacağım.
i P

3
@Pi owhere'in modulo olduğu %söyleniyor ... geri kalanı .
obataku

1
İşte bunun "kopyası" olan başka bir konu: stackoverflow.com/questions/1082917/… Sadece bu %soruna referans olması için .
leetNightshade

Eğer sadece ikiye bölünüyorsanız, o zaman kullanmak daha iyi bir fikir olabilir ve:(-1) & 8 == 7
Henricus

Yanıtlar:


75

Her şeyden önce, buna güvenemeyeceğinizi belirtmek isterim (-1) % 8 == -1. güvenebileceğiniz tek şey budur (x / y) * y + ( x % y) == x. Ancak geri kalanın negatif olup olmadığı uygulama tanımlıdır .

Şimdi neden buradaki şablonları kullanalım? İnsler ve uzunlar için bir aşırı yük olur.

int mod (int a, int b)
{
   int ret = a % b;
   if(ret < 0)
     ret+=b;
   return ret;
}

ve şimdi mod (-1,8) gibi çağırabilirsiniz ve 7 olarak görünecektir.

Düzenleme: Kodumda bir hata buldum. B negatifse işe yaramaz. Bu yüzden bunun daha iyi olduğunu düşünüyorum:

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;
}

Referans: C ++ 03 paragraf 5.6 madde 4:

İkili / işleç bölümü verir ve ikili% işleci, ilk ifadenin ikinciye bölünmesinden kalanı verir. / Veya% 'nin ikinci işleneni sıfır ise davranış tanımsızdır; aksi takdirde (a / b) * b + a% b, a'ya eşittir. Her iki işlenen de negatif değilse, geri kalanı negatif değildir; değilse, kalanın işareti uygulama tanımlıdır .


2
@Ohmu: Evet, bu C ++ standardında. <quote> İntegral işlenenler için / operatörü, herhangi bir kesirli bölüm atılmış olarak cebirsel bölümü verir; a / b bölümü sonuç türünde gösterilebilirse, (a / b) * b + a% b, a'ya eşittir. </quote>
Ben Voigt

5
-1. Bu uygulamanın tanımlanmasının üzerinden 11 yıl geçti. ISO 9899: 1999 bunu tanımladı ve ne yazık ki kötü tanımı seçti.
R .. GitHub DUR YARDIMCI ICE

3
@Armen: <quote> dipnotunu rahatlıkla sildiniz ... tamsayı bölümü ISO Fortran standardı, ISO / IEC 1539: 1991'de tanımlanan kurallara uyuyor, burada bölüm her zaman sıfıra yuvarlanır </quote>. Yeni C ++ standardı, bu davranışı, Fortran ve C gibi "tercih edilen" den zorunlu hale yükseltir.
Ben Voigt

2
@Armen: Eski şartname bozuldu, ancak kırılma işaret sorunundan farklı ve yeni ifadeye bakana kadar gözden kaçırmak kolay. C ++ 03, "a / b bölümü sonuç türünde gösterilebilirse" içermiyordu, bu da INT_MIN / -1(ikinin tamamlayıcı uygulamalarında) için sorunlara neden oluyor . Eski spesifikasyona göre, kimliğin tutması için (16 bitlik tipin aralığında değil, yuck!) Değerini -32768 % -1değerlendirmek gerekebilir -65536.
Ben Voigt

1
re "Ancak, kalan negatif olsun ya da olmasın uygulama tanımlıdır.", C ++ 11 tamsayı bölmenin 0'a doğru
yuvarlanacağını

13

İşte BOTH OPERANDS için pozitif OR negatif tamsayı VEYA kesirli değerleri işleyen bir C işlevi.

#include <math.h>
float mod(float a, float N) {return a - N*floor(a/N);} //return in range [0, N)

Bu kesinlikle matematiksel açıdan en zarif çözümdür. Ancak, tam sayıları işlemede sağlam olup olmadığından emin değilim. Bazen, int -> fp -> int dönüştürülürken kayan nokta hataları ortaya çıkar.

Bu kodu int olmayanlar için ve int için ayrı bir işlev kullanıyorum.

NOT: N = 0'ı tuzağa düşürmeniz gerekir!

Test cihazı kodu:

#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;
}

(Not: Kodu doğrudan CodePad'den derleyip çalıştırabilirsiniz: http://codepad.org/UOgEqAMA )

Çıktı:

fmodf (-10.2, 2.0) = -0.20 == BAŞARISIZ!

10,2 mod 2,0 = 0,2
10,2 mod -2,0 = -1,8
-10,2 mod 2,0 = 1,8
-10,2 mod -2,0 = -0,2


Maalesef bu tamsayılarla çalışmaz. Kullanmanıza izin vermek için bölünmeden önce kayan noktaya dönüştürülmeleri gerekir floor(). Ayrıca, float'a dönüştürdüğünüzde hassasiyeti kaybedebilirsiniz: Deneyin (float)1000000001/3, sonuçlara şaşıracaksınız!
cmaster - monica

9

Bjarne Stroustrup'un modulo operatörü olarak değil , kalan operatör %olarak etiketlediğini fark ettim .

Bahse girerim ANSI C & C ++ şartnamelerindeki resmi adıdır ve terminolojinin kötüye kullanımı ortaya çıkmıştır. Bunu bir gerçek için bilen var mı?

Ama durum buysa, o zaman C'nin fmodf () işlevi (ve muhtemelen diğerleri) çok yanıltıcıdır. fremf (), vb. olarak etiketlenmeleri gerekir.


1
C11 standardı (veya kesin olarak nihai genel taslak ) "modulo" dan altı kez bahseder, ancak yalnızca çeşitli türlerin temsiliyle ilgili olarak. Kalan operatörle ( %) ilgili olarak "modulo" dan bir kez bile bahsetmez .
Nisse Engström

7

Pozitif modülü bulmanın en basit genel işlevi şudur: x'in hem pozitif hem de negatif değerleri üzerinde çalışır.

int modulo(int x,int N){
    return (x % N + N) %N;
}

6

Tam sayılar için bu basittir. Sadece yap

(((x < 0) ? ((x % N) + N) : x) % N)

Nbunun türünün olumlu ve temsil edilebilir olduğunu varsayıyorum x. En sevdiğiniz derleyiciniz bunu, assembler'da sadece bir mod işlemiyle sonuçlanacak şekilde optimize edebilmelidir.


3
Çalışmaz: için int x=-9001; unsigned int N=2000;değil 999. 2295 verir
Hubert Kario

1
@HubertKario Belki tekrar kontrol edin? Modulo 2000'in 2295 vermesi mümkün değildir, bir hata yapmış olmalısınız.
sam hocevar

2
@SamHocevar: Bence buradaki problem garip C tamsayı terfi kuralları. işaretli işaretsiz olarak yükseltme ve işaretsiz olarak negatif işaretli tamsayı değeri yükseltme C'de tanımsız davranışa neden olur
datenwolf

1
Ben çok daha basit (ve daha verimli) formu olurdu: (x < 0) ? (x % N + N) : (x % N).
Chris Nolet

3

Bir matematikçi için en iyi çözüm Python kullanmaktır.

C ++ operatörü aşırı yüklemesinin bununla çok az ilgisi vardır. Yerleşik türler için operatörleri aşırı yükleyemezsiniz. İstediğiniz şey basitçe bir işlevdir. Elbette, bu işlevi tüm ilgili türler için yalnızca 1 parça kodla uygulamak için C ++ şablonunu kullanabilirsiniz.

Standart C kitaplığı fmod, eğer adı doğru hatırlıyorsam, kayan nokta türleri için sağlar.

Tamsayılar için, her zaman negatif olmayan kalanı (Öklid bölünmesine karşılık gelir) şu şekilde döndüren bir C ++ işlev şablonu tanımlayabilirsiniz ...

#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);
}

... ve mod(a, b)bunun yerine sadece yazın a%b.

Burada türün Integerişaretli bir tamsayı türü olması gerekir.

Kalanın işaretinin bölenin işareti ile aynı olduğu ortak matematik davranışını istiyorsanız, o zaman örneğin

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 ); }

… Aynı kısıtlamayla Integer, bu imzalı bir tür.


¹ Çünkü Python'un tamsayı bölümü negatif sonsuza doğru yuvarlanır.


senin kodun benim düzenlememden önce benimki ile aynı hataya sahip görünüyor. Ya b negatifse? :)
Armen Tsirunyan

1
@Armen: teşekkürler! ama sadece bunun için düzenleme yapmak için çok tembelim ... :-)
Şerefe ve hth. - Alf

@ArmenTsirunyan: rsonuç a= r + b*(a/b)true olmalıdır. tamsayı bölümü nasıl uygulanırsa uygulansın b*something, bir katıdır b. bu, rnegatif olsa bile geçerli bir modulo sonucu oluşturur. buna abs ( b) ekleyebilirsiniz ve bu yine de geçerli bir modulo sonucu olacaktır.
Şerefe ve hth. - Alf

2
@downvoters: Seçilen "çözüm" artık C ++ 11'deki yeni garantiler nedeniyle yanlış yorum içeriyor olsa da, bu cevap hala doğru. Hâlâ doğru olan bir yanıta olumsuz oy vermek çok ironik. Herhangi bir sebep gösterilmeksizin, neredeyse mutlak bir cehalet derecesine sahip en az 2 çağrışımsal kişinin bu sorunun yorumunu okuduğunu ve düşüncesizce olumsuz oy kullandığını varsaymak zorunda. Lütfen olumsuz oylarınızı açıklayın.
Şerefe ve hth. - Alf

1
Matematiksel olarak istenen sonuç, kalanın sıfır olması veya bölen (payda) ile aynı işarete sahip olmasıdır. Bölen negatifse, kalan sıfır veya negatif olmalıdır. C / C ++ uygulaması, kalanın sıfır olmasına veya temettü (pay) ile aynı işarete sahip olmasına neden olur.
rcgldr

2

Oh, ben de bunun için tasarımdan nefret ediyorum ....

Temettüyü aşağıdaki gibi imzasız olarak dönüştürebilirsiniz:

unsigned int offset = (-INT_MIN) - (-INT_MIN)%divider

result = (offset + dividend) % divider

ofset modülün (-INT_MIN) katına en yakın olduğu yerde, bu yüzden eklemek ve çıkarmak moduloyu değiştirmeyecektir. İşaretsiz türe sahip olduğunu ve sonucun tamsayı olacağını unutmayın. Maalesef arifmetik taşmaya neden oldukları için INT_MIN ... (- ofset-1) değerlerini doğru şekilde dönüştüremiyor. Ancak bu yöntem, sabit bölücü ile çalışırken işlem başına yalnızca tek bir ek aritmetik avantaja sahiptir (ve koşulsuz), bu nedenle DSP benzeri uygulamalarda kullanılabilir.

Bölücünün 2 N (ikinin tamsayı gücü) olduğu özel bir durum vardır, bunun için modulo basit aritmetik ve bitsel mantık kullanılarak hesaplanabilir.

dividend&(divider-1)

Örneğin

x mod 2 = x & 1
x mod 4 = x & 3
x mod 8 = x & 7
x mod 16 = x & 15

Daha yaygın ve daha az karmaşık olan yol, bu işlevi kullanarak modulo elde etmektir (yalnızca pozitif bölücü ile çalışır):

int mod(int x, int y) {
    int r = x%y;
    return r<0?r+y:r;
}

Negatif ise bu sadece doğru sonuçtur.

Ayrıca kandırabilirsiniz:

(p% q + q)% q

Çok kısadır, ancak genellikle yavaş olan% iki kullanır.


2

Bu soruna başka bir çözümün int yerine long türündeki değişkenler için kullanılacağına inanıyorum.

% Operatörünün bazı sorunlara neden olan negatif bir değer döndürdüğü bir kod üzerinde çalışıyordum ([0,1] üzerinde tek tip rastgele değişkenler oluşturmak için gerçekten negatif sayılar istemiyorsunuz :)), ancak değişkenleri uzun yazın, her şey sorunsuz çalışıyordu ve sonuçlar python'da aynı kodu çalıştırırken aldığım sonuçlarla eşleşiyordu (benim için önemli çünkü birkaç platformda aynı "rastgele" sayıları üretebilmek istiyordum.


2

İşte bu Microsoft Araştırma makalesine göre eski bir soruya yeni bir cevap ve buradaki referanslara .

C11 ve C ++ 11 itibaren, bir anlambilim o Not divolmuştur sıfıra doğru kesme (bakınız [expr.mul]/4). Üstelik, Dbölü d, C ++ 11 teminat bölüm ile ilgili aşağıdaki qTve geri kalanı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));

signumargümanı olup olmadığına bağlı olarak, 0, -1 +1 haritalar <==,> 0 (bakınız daha bu soru-cevap kaynak kodu).

Kesik bölme ile, kalanın işareti temettü işaretine eşittirD , yani -1 % 8 == -1. C ++ 11 ayrıca std::divüyelerle quotve remkesilmiş bölüme göre bir yapı döndüren bir işlev sağlar .

Mümkün olan başka tanımlar da vardır, örneğin, sözde tabana bölünmüş bölme , yerleşik kesik bölme açısından tanımlanabilir

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));

Tabana bölünmüş bölme ile, geri kalanın işareti bölenin işaretine eşittird . Haskell ve Oberon gibi dillerde, katlanmış bölme için yerleşik operatörler vardır. C ++ 'da, yukarıdaki tanımları kullanarak bir işlev yazmanız gerekir.

Yine başka bir yol, yerleşik kesik bölünme açısından da tanımlanabilen Öklid bölünmesidir

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) != -1);

Öklid bölünmesi ile, geri kalanın işareti her zaman pozitiftir .


2

Dal içermeyen ve sadece 1 mod kullanan bir çözüm için aşağıdakileri yapabilirsiniz

// 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);
}

1
/ * Uyarı: makro modu, argümanlarının yan etkilerini birden çok kez değerlendirir. * /
# tanımlı mod (r, m) (((r)% (m)) + ((r) <0)? [m): 0)

... ya da denklik sınıfı için herhangi bir temsilci almaya alışabilirsiniz.


2
"Denklik sınıfı için herhangi bir temsilci almaya alışın" ?! Bu saçma. Eğer istersen, sadece orijinal "temsilciyi" kullanabilirsin r. %Operatör denklik sınıfları ile ilgisi yoktur. Kalan operatördür ve kalanı cebirsel olarak negatif olmayacak ve bölenden daha küçük olacak şekilde iyi tanımlanmıştır. Ne yazık ki C bunu yanlış şekilde tanımladı. Yine de, en iyi cevaplardan birine sahip olduğunuz için +1.
R .. GitHub BUZA YARDIM ETMEYİ DURDUR

0

C ++ için örnek şablon

template< class T >
T mod( T a, T b )
{
    T const r = a%b;
    return ((r!=0)&&((r^b)<0) ? r + b : r);
}

Bu şablonla, geri kalan C ++ davranışının sıfır olması veya temettüyle aynı işarete sahip olması yerine, geri kalan kalan sıfır olacaktır veya bölenle (payda) aynı işarete (negatif sonsuzluğa yuvarlama eşdeğeri) sahip olacaktır ( pay) (sıfıra yuvarlama eşdeğeri).



-1
unsigned mod(int a, unsigned b) {
    return (a >= 0 ? a % b : b - (-a) % b);
}

-1

Bu çözüm (kullanım için mod pozitif ), negatif bölme veya kalan işlemlerin topluca alınmasını önler:

int core_modulus(int val, int mod)
{
    if(val>=0)
        return val % mod;
    else
        return val + mod * ((mod - val - 1)/mod);
}

-2

Yapardım:

((-1)+8) % 8 

Bu, moduloyu yapmadan önce istenen şekilde 7 veren son numarayı birinciye ekler. Bu, -8'e kadar herhangi bir sayı için çalışmalıdır. -9 için 2 * 8 ekleyin.


2
Ve değeri olabilecek bir değişken için -99999?
Keith Thompson
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.