Negatif sayı modu beynimi eritiyor


189

Ben yuvarlak döngü böylece bir dizi pozisyon almak için bir tamsayı mod çalışıyorum. Yapmak i % arrayLengthpozitif sayılar için iyi çalışır, ancak negatif sayılar için her şey ters gider.

 4 % 3 == 1
 3 % 3 == 0
 2 % 3 == 2
 1 % 3 == 1
 0 % 3 == 0
-1 % 3 == -1
-2 % 3 == -2
-3 % 3 == 0
-4 % 3 == -1

bu yüzden bir uygulamaya ihtiyacım var

int GetArrayIndex(int i, int arrayLength)

öyle ki

GetArrayIndex( 4, 3) == 1
GetArrayIndex( 3, 3) == 0
GetArrayIndex( 2, 3) == 2
GetArrayIndex( 1, 3) == 1
GetArrayIndex( 0, 3) == 0
GetArrayIndex(-1, 3) == 2
GetArrayIndex(-2, 3) == 1
GetArrayIndex(-3, 3) == 0
GetArrayIndex(-4, 3) == 2

Bunu daha önce yaptım ama nedense bugün beynimi eritiyor :(


Matematiksel modül hakkındaki tartışmaya math.stackexchange.com/questions/519845/…
PPC

Yanıtlar:


281

Her zaman kendi modfonksiyonumu kullanıyorum,

int mod(int x, int m) {
    return (x%m + m)%m;
}

Tabii ki, modül işlemine iki çağrı yapmaktan rahatsız olursanız,

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

veya bunların varyantları.

Çalışmasının nedeni "% x m" nin daima [-m + 1, m-1] aralığında olmasıdır. Yani eğer negatifse, ona m eklemek, modulo m değerini değiştirmeden pozitif aralığa koyacaktır.


7
Not: tam sayı teorik bütünlüğü için en üste "if (m <0) m = -m;" diyen bir satır eklemek isteyebilirsiniz. bu durumda "arrayLength" muhtemelen her zaman pozitif olduğundan önemli değildir.
ShreevatsaR

4
M değerini kontrol edecekseniz, sıfırı da hariç tutmalısınız.
billpg

6
@RuudLenders: Hayır ise, x = -5 ve m = 2 olduğunda, r = x%molduğu -1, bundan sonra, r+mbir 1. While döngüsü gerekli değildir. Mesele şu ki (cevapta yazdığım gibi), x%mher zaman kesinlikle daha büyüktür -m, bu yüzden molumlu yapmak için en fazla bir kez eklemeniz gerekir .
ShreevatsaR

4
@dcastro: Ben do 8. olmaya -12 mod -10 istiyorum Bu bir temsilci seçmek takdirde, matematikte en yaygın kongre olduğu riçin amodülo b, o zaman böyle olduğunu 0 ≤ r <b |.
ShreevatsaR

8
+1. Herhangi bir dilin negatif bir modül için ne yaptığını umursamıyorum - 'en az negatif olmayan kalıntı' matematiksel bir düzenlilik gösterir ve belirsizlikleri ortadan kaldırır.
Brett Hale

80

C # ve C ++ 'ın% operatörünün aslında bir modulo DEĞİLDİR, geri kalanı olduğunu lütfen unutmayın. Sizin durumunuzda istediğiniz modulo için formül:

float nfmod(float a,float b)
{
    return a - b * floor(a / b);
}

Bunu C # (veya C ++) ile yeniden kodlamanız gerekir, ancak bu modulo ve geri kalan değil.


21
"Lütfen C ++ 'ın% operatörünün aslında bir modulo DEĞİLDİR, geri kalanı olduğunu unutmayın." Teşekkürler, şimdi mantıklı, neden her zaman negatif sayılarla düzgün çalışmadığını merak ediyorum.
leetNightshade

2
"C ++ 'ın% operatörünün aslında bir modulo DEĞİLDİR, geri kalanı olduğunu lütfen unutmayın." Bunun doğru olduğunu düşünmüyorum ve neden bir modulo'un diğerlerinden farklı olduğunu anlamıyorum. Modulo Operasyonu Wikipedia sayfasında da böyle yazıyor. Sadece programlama dilleri negatif sayılara farklı davranır. C # 'daki modulo operatörü açık olarak "sıfırdan" (% -9% 4 = -1) kalanları sayar (çünkü 4 * -2, -1'lik bir farkla -8'dir), başka bir tanım ise -9% 4'ü +3 olarak değerlendirir. -4 * 3, -12, kalan +3 (Google'ın arama işlevinde olduğu gibi, arka uç dilinden emin değilim).
Tyress

18
Tyress, modül ve kalan arasında bir fark var. Örneğin: -21 mod 4 is 3 because -21 + 4 x 6 is 3. Ama -21 divided by 4 gives -5a remainder of -1. Pozitif değerler için fark yoktur. Lütfen bu farklılıklar hakkında kendinizi bilgilendirin. Ve her zaman Wikipedia'ya güvenme :)
Петър Петров

2
Neden kimse modulo yerine kalan fonksiyonu kullanmak istesin ki? Neden %geri kalanlar yaptılar ?
Aaron Franke

4
@AaronFranke - hızlı bir bölüm ve kalan üretmek için bölme donanımına sahip önceki cpus'un mirası - ve bu donanımın negatif bir temettü verdiği şey budur. Dil sadece donanımı yansıtıyordu. Çoğu zaman programcılar pozitif temettülerle çalışıyordu ve bu tuhaflığı görmezden geliyorlardı. Hız çok önemliydi.
ToolmakerSteve

16

%Yalnızca bir kez kullanılan tek hat uygulaması :

int mod(int k, int n) {  return ((k %= n) < 0) ? k+n : k;  }

1
bu doğru mu? Kimsenin kabul ettiği gibi görmüyorum, ne de yorum var. Örneğin. mod (-10,6) 6 değerini döndürür. Bu doğru mu? 4 döndürmemeli mi?
John Demetriou

3
@JohnDemetriou Sayılarınızın ikisi de yanlış: (A) 2 döndürmeli ve (B) 2 döndürüyor; kodu çalıştırmayı deneyin. Madde (A): mod(-10, 6)elle bulmak için, cevap menzil içinde oluncaya kadar 6'yı tekrar tekrar ekler veya çıkarırsınız [0, 6). Bu gösterim "solda kapsayıcı ve sağda münhasır" anlamına gelir. Bizim durumumuzda, iki kez 6 ekleyerek 2 veriyoruz. Kod oldukça basit ve doğru olduğunu görmek kolay: ilk önce, nyukarıdaki gibi bir nkısa durması dışında, yukarıdaki gibi toplama / çıkarma eşdeğerini yapar . olumsuz tarafı. Bu durumda düzeltiriz. Orada: yorumlar :)
Evgeni Sergeev

1
Bu arada, bir single'ı kullanmanın %iyi bir fikir olabilmesinin bir nedeni . Daha Hızlı Yönetilen Kod Yazma: Şeylerin Maliyetini Öğrenin başlıklı makalede yönetilen kodda maliyetin ne olduğu tablosuna bakın . Kullanmak , tabloda listelenenlere benzer şekilde pahalıdır : toplama veya çıkarma işleminden yaklaşık 36 kat daha pahalı ve çarpma işleminden yaklaşık 13 kat daha pahalıdır. Tabii ki, bu kodunuzun ne yaptığının merkezinde değilse büyük bir anlaşma yok. %int div
Evgeni Sergeev

2
Ama tek %bir test ve atlamadan daha pahalı, özellikle de kolayca tahmin edilemiyorsa?
Medinoc

6

ShreevatsaR'ın cevabı, "if (m <0) m = -m;" ekleseniz bile, negatif temettü / bölenleri hesaba katarsanız tüm durumlar için işe yaramaz.

Örneğin, -12 mod -10 8 olacak ve -2 olmalıdır.

Aşağıdaki uygulama hem olumlu hem de negatif temettüler / bölücüler için çalışacak ve diğer uygulamalara (Java, Python, Ruby, Scala, Şema, Javascript ve Google'ın Hesap Makinesi) uygun olacaktır:

internal static class IntExtensions
{
    internal static int Mod(this int a, int n)
    {
        if (n == 0)
            throw new ArgumentOutOfRangeException("n", "(a mod 0) is undefined.");

        //puts a in the [-n+1, n-1] range using the remainder operator
        int remainder = a%n;

        //if the remainder is less than zero, add n to put it in the [0, n-1] range if n is positive
        //if the remainder is greater than zero, add n to put it in the [n-1, 0] range if n is negative
        if ((n > 0 && remainder < 0) ||
            (n < 0 && remainder > 0))
            return remainder + n;
        return remainder;
    }
}

XUnit kullanarak test takımı:

    [Theory]
    [PropertyData("GetTestData")]
    public void Mod_ReturnsCorrectModulo(int dividend, int divisor, int expectedMod)
    {
        Assert.Equal(expectedMod, dividend.Mod(divisor));
    }

    [Fact]
    public void Mod_ThrowsException_IfDivisorIsZero()
    {
        Assert.Throws<ArgumentOutOfRangeException>(() => 1.Mod(0));
    }

    public static IEnumerable<object[]> GetTestData
    {
        get
        {
            yield return new object[] {1, 1, 0};
            yield return new object[] {0, 1, 0};
            yield return new object[] {2, 10, 2};
            yield return new object[] {12, 10, 2};
            yield return new object[] {22, 10, 2};
            yield return new object[] {-2, 10, 8};
            yield return new object[] {-12, 10, 8};
            yield return new object[] {-22, 10, 8};
            yield return new object[] { 2, -10, -8 };
            yield return new object[] { 12, -10, -8 };
            yield return new object[] { 22, -10, -8 };
            yield return new object[] { -2, -10, -2 };
            yield return new object[] { -12, -10, -2 };
            yield return new object[] { -22, -10, -2 };
        }
    }

İlk olarak, bir modfonksiyon genellikle pozitif modül ile çağrılır ( arrayLengthburada cevaplanan orijinal sorudaki değişkeni not edin , ki bu muhtemelen asla negatif değildir), bu nedenle işlevin negatif modül için çalışması gerekmez. (Bu yüzden, cevabın kendisinde değil,
cevabımla

3
İkincisi, olumsuz bir modül için ne yapılması bir konvansiyon meselesidir. Bkz. Örneğin Wikipedia . "Genellikle, sayı teorisinde, pozitif kalan her zaman seçilir" ve ben de bu şekilde öğrendim (Burton İlköğretim Sayı Teorisinde ). Knuth bunu bu şekilde tanımlar (özellikle r = a - b floor(a/b)her zaman pozitiftir). Örneğin bilgisayar sistemleri arasında bile Pascal ve Maple, onu her zaman pozitif olarak tanımlar.
ShreevatsaR

@ShreevatsaR Öklid tanımının sonucun her zaman olumlu olacağını ifade ettiğini biliyorum - ama çoğu modern mod uygulamasının negatif bölen "n" için [n + 1, 0] aralığında bir değer döndüreceği izlenimi altındayım, yani -12 mod -10 = -2 anlamına gelir. Google Hesap Makinesi , Python , Ruby ve Scala'ya baktım ve hepsi de bu sözleşmeyi takip ediyor.
Ocak'ta dcastro

Ayrıca, listeye eklemek için: Şema ve Javascript
dcastro

1
Yine, bu hala iyi bir okuma. "Her zaman pozitif" tanım (cevabım) ALGOL, Dart, Maple, Pascal, Z3, vb. İle tutarlıdır. Excel, Perl, Python, R, Ruby, Tcl, vb. Her ikisi de "temettü işareti" ile tutarsızdır: AWK, bash, bc, C99, C ++ 11, C #, D, Eiffel, Erlang, Go, Java , OCaml, PHP, Rust, Scala, Swift, VB, x86 meclisi, vb. Gerçekten bir kuralın "doğru" ve diğerlerinin "yanlış" olduğunu nasıl iddia edebileceğinizi görmüyorum.
ShreevatsaR

6

Biraz anlayış katıyorum.

By Öklit tanımı mod sonucu her zaman pozitif olmalıdır.

Ör:

 int n = 5;
 int x = -3;

 int mod(int n, int x)
 {
     return ((n%x)+x)%x;
 }

Çıktı:

 -1

15
Kafam karıştı ... sonucun her zaman pozitif olması gerektiğini söylüyorsunuz, ancak çıktıyı şöyle listeliyorsunuz -1?
Jeff B

@JeffBridgman Öklid tanımına dayalı olduğunu söyledim. “Kalanlar için biri olumsuz diğeri pozitif olmak üzere iki olası seçenek ve bölüm için de iki olası seçenek var. Genellikle sayı teorisinde, the positive remainder is always chosenancak programlama dilleri, a ve / veya n'nin işaretine ve diline bağlı olarak seçim yapar. [5] Standart Pascal ve Algol68, negatif bölenler için bile olumlu bir kalan (veya 0) verir ve C90 gibi bazı programlama dilleri, n veya
a'nın

5

İki baskın cevabı karşılaştırma

(x%m + m)%m;

ve

int r = x%m;
return r<0 ? r+m : r;

Aslında hiç kimse birincisinin bir OverflowExceptionsüre atabileceğinden , ikincisinin yapamayacağından bahsetmedi. Daha da kötüsü, varsayılan işaretlenmemiş bağlamda, ilk yanıt yanlış yanıtı döndürebilir ( mod(int.MaxValue - 1, int.MaxValue)örneğin bkz .). Yani ikinci cevap sadece daha hızlı değil aynı zamanda daha doğru görünüyor.


4

Modülünüzü (arrayLength)% negatif sonucuna eklemeniz yeterlidir.


4

Daha fazla performansa duyarlı geliştiriciler için

uint wrap(int k, int n) ((uint)k)%n

Küçük bir performans karşılaştırması

Modulo: 00:00:07.2661827 ((n%x)+x)%x)
Cast:   00:00:03.2202334 ((uint)k)%n
If:     00:00:13.5378989 ((k %= n) < 0) ? k+n : k

Dökümden uint'e performans maliyeti gelince buraya bir göz atın


3
Galiba -3 % 10ya olmak -3 veya negatif olmayan bir sonuç aranıyor yana 7. 7 gerektiğini cevap olacaktır. Uygulamanız 3 döndürür. Her iki parametreyi de değiştirmeli uintve kadroyu kaldırmalısınız.
Eski Stack Overflow'u

5
İmzasız aritmetik sadece nikisinin gücü ise eşdeğerdir , bu durumda (uint)k & (n - 1)derleyici sizin için zaten yapmazsa (ve derleyiciler genellikle bunu anlayacak kadar akıllıdır) mantıksal ve ( ) kullanabilirsiniz .
j_schultz

2

Ben bu iş parçacığı üzerinde Peter N Lewis tarafından sunulan hüner gibi : "Eğer n sınırlı bir aralığı varsa, o zaman basitçe mutlak değeri daha büyük [divizör] bilinen bir sabit katını ekleyerek istediğiniz sonucu alabilirsiniz asgari."

Yani derece cinsinden bir d değerine sahipsem ve almak istiyorum

d % 180f

ve d negatifse sorunlardan kaçınmak istiyorum , bunun yerine ben sadece bunu yapmak:

(d + 720f) % 180f

Bu, d'nin negatif olmasına rağmen , asla -720'den daha olumsuz olmayacağı biliniyor.


2
-1: yeterince genel değil, (ve daha genel bir çözüm vermek çok kolaydır).
Evgeni Sergeev

4
Bu aslında çok faydalı. anlamlı bir aralığınız olduğunda, bu hesaplamayı basitleştirebilir. benim durumumda math.stackexchange.com/questions/2279751/…
M.kazem Akhgary

Tam olarak, bunu dayOfWeek hesaplaması için kullandı (-6 ila +6 arasında bilinen aralık) ve iki tane ile kaydetti %.
NetMage

@EvgeniSergeev +0 benim için: OP sorusunu cevaplamıyor ancak daha spesifik bir bağlamda yardımcı olabilir (ancak yine de soru bağlamında)
Erdal G.

1

% Operatörünün belgelenmiş davranışına aykırı bir davranış bekliyorsunuz - muhtemelen, daha çok alışık olduğunuz başka bir dilde çalışacak şekilde çalışmasını bekliyorsunuz. Dokümantasyon c # devletler (vurgu benim) tarih:

Tamsayı tiplerinin işlenenleri için% b'nin sonucu, a - (a / b) * b tarafından üretilen değerdir. Sıfır olmayan kalanın işareti, soldaki işlenenin işareti ile aynıdır.

İstediğiniz değer ekstra bir adımla hesaplanabilir:

int GetArrayIndex(int i, int arrayLength){
    int mod = i % arrayLength;
    return (mod>=0) : mod ? mod + arrayLength;
}

1

Dcastro'nun cevabının tek bir satır uygulaması ( diğer dillerle en uyumlu):

int Mod(int a, int n)
{
    return (((a %= n) < 0) && n > 0) || (a > 0 && n < 0) ? a + n : a;
}

%Operatör kullanımını korumak isterseniz (C # 'da yerel operatörleri aşırı yükleyemezsiniz):

public class IntM
{
    private int _value;

    private IntM(int value)
    {
        _value = value;
    }

    private static int Mod(int a, int n)
    {
        return (((a %= n) < 0) && n > 0) || (a > 0 && n < 0) ? a + n : a;
    }

    public static implicit operator int(IntM i) => i._value;
    public static implicit operator IntM(int i) => new IntM(i);
    public static int operator %(IntM a, int n) => Mod(a, n);
    public static int operator %(int a, IntM n) => Mod(a, n);
}

Kullanım durumu, her ikisi de çalışır:

int r = (IntM)a % n;

// Or
int r = a % n(IntM);

0

Bölgeniz olumluysa, buradaki tüm cevaplar harika çalışıyor, ancak tam değil. İşte her zaman bir aralıkta dönen benim uygulama [0, b), böylece çıkış işareti bölen işareti ile aynı olacak, çıkış aralığı için bitiş noktası olarak negatif bölücüler izin.

PosMod(5, 3)döner 2
PosMod(-5, 3)döner 1
PosMod(5, -3)döner -1
PosMod(-5, -3)döner-2

    /// <summary>
    /// Performs a canonical Modulus operation, where the output is on the range [0, b).
    /// </summary>
    public static real_t PosMod(real_t a, real_t b)
    {
        real_t c = a % b;
        if ((c < 0 && b > 0) || (c > 0 && b < 0)) 
        {
            c += b;
        }
        return c;
    }

( real_therhangi bir sayı türü olabilir)

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.