Programlama dili tasarımcıları modulo işleminin sonucunun ne olduğuna karar verirken hangi mantık kullanılır?


9

Geçiyor Modülo operasyonu (arasındaki farkı araştırırken girdiğim caddesi remvemod ben rastladım):

Matematikte modulo işleminin sonucu Öklid bölümünün geri kalan kısmıdır. Ancak, başka sözleşmeler de mümkündür. Bilgisayarlar ve hesap makineleri sayıları depolamanın ve göstermenin çeşitli yollarına sahiptir; dolayısıyla modulo operasyonu tanımlamaları programlama diline ve / veya temel donanıma bağlıdır.

Sorular:

  • Öklid bölümünden geçerek bu operasyonun geri kalanının daima pozitif (veya 0) olduğunu buldum. Temeldeki bilgisayar donanımının hangi sınırlaması programlama dili tasarımcılarını matematikten ayırmaya zorlar?
  • Her programlama dili, modulo işleminin sonucunun işaretini aldığı önceden tanımlanmış veya tanımlanmamış bir kurala sahiptir. Bu kurallar yapılırken hangi gerekçe kabul edilir? Ve eğer temel donanım endişe ise, programlama dilinden bağımsız olarak kurallar buna göre değişmemelidir?

1
Benim kodda hemen hemen her zaman kalan değil modulo gerekir. Kalanın neden bu kadar popüler olduğunu bilmiyorum.
CodesInChaos

8
İlgili Fark nedir? Remainder vs Modulus - Eric Lippert'in Blogu (C # tasarımcılarından biri tarafından, ancak bu karar verildikten sonra takıma katıldığına inanıyorum)
CodesInChaos

1
Wikipedia makalesini okumaya devam ederseniz (alıntıladığınız bölümün ötesinde), oldukça iyi alıntıladığınızı açıklar. Bu açıklamaya ne dersin?
Robert Harvey

1
İlgili bir soru, bu işlemlerden hangisinin doğrudan CPU talimatlarına eşlendiğidir. C'de, donanıma mümkün olduğunca çok platformda doğrudan eşleme felsefesine uyan uygulama tanımlanmıştır. Bu yüzden CPU'lar arasında farklılık gösterebilecek şeyler belirtmez.
CodesInChaos

5
@BleedingFingers Programlama genellikle sıfıra doğru giden bir tamsayı bölümü kullanır, örn (-3)/2 == -1. Bu tanım faydalı olabilir. %Bu bölümle tutarlı olmak istediğinizde C # 'da kullanılan x == (x/y)*y + x % ytanım ile sonuçlanırsınız %.
CodesInChaos

Yanıtlar:


7

Tüm modern bilgisayarların donanımı, herhangi bir işaretin (veya önemsiz) performans etkisi olmadan mod işlemlerini gerçekleştirmek için yeterince güçlüdür. Nedeni bu değil.

Çoğu bilgisayar dilinin ortak beklentisi (div b) * b + (a mod b) = a. Başka bir deyişle, birlikte düşünülen div ve mod, bir sayıyı tekrar güvenilir bir şekilde bir araya getirilebilecek parçalara böler. Bu gereksinim C ++ standardında açıktır. Kavram çok boyutlu dizilerin indekslenmesi ile yakından ilgilidir. Sık kullandım.

Bundan div ve mod'un, eğer b pozitifse (genellikle olduğu gibi) işaretini koruyacağı görülebilir.

Bazı diller mod ile ilgili ve diğer bazı matematiksel gerekçelere sahip bir 'rem ()' işlevi sağlar. Bunu hiç kullanmam gerekmedi. Bkz. Örneğin Gnu C'deki frem () [düzenlenmiş]


Ben düşünüyorum rem(a,b)olması daha likley mod(a,b)o pozitif ise veya mod(a,b) + beğer değilse.
user40989

3
(a div b) * b + (a mod b) = a- bu, çok fazla. Aslında, Wikipedia'nın onu Öklid bölünmesinde negatif sayılara genişletmeyi nasıl tanımladığını (özellikle "Kalan, asla negatif olamayacak dört sayıdan sadece biridir") beni karıştırıyor çünkü her zaman kalanın negatif olabileceği öğretildi o seviyedeki her matematik dersinde.
Izkata

@ user40989: Asla kullanmayacağımı söyledim. Düzenlemeye bakın!
david.pfx

4

Genellikle istediğiniz programlama için X == (X/n)*n + X%n; bu nedenle modulo'nun nasıl tanımlandığı, tamsayı bölümünün nasıl tanımlandığına bağlıdır.

Bunu göz önünde bulundurarak, gerçekten soruyorsunuz " Dil tasarımcıları programlanırken tamsayı bölümünün nasıl çalıştığına karar verirken hangi mantık kullanılır? "

Aslında yaklaşık 7 seçenek var:

  • negatif sonsuza yuvarlanır
  • pozitif sonsuza yuvarlanır
  • sıfıra yuvarla
  • "en yakına yuvarla" nın birkaç sürümü (0,5 gibi bir şeyin yuvarlanma biçimindeki farklılıklarla)

Şimdi düşünün -( (-X) / n) == X/n. Başka bir şey tutarsız (kayan nokta için doğrudur) ve mantıksız (hataların olası bir nedeni ve aynı zamanda potansiyel olarak kaçırılmış bir optimizasyon) gibi göründüğü için bunun doğru olmasını isterim. Bu, tamsayı bölünmesi (her iki sonsuzluğa doğru yuvarlama) için ilk 2 seçimi istenmeyen kılar.

"Yuvarlakdan en yakına" seçeneklerinin tümü programlama için, özellikle de bitmapler gibi bir şey yaptığınızda (örn. offset = index / 8; bitNumber = index%8;) Boyunda bir ağrıdır .

Bu, sıfıra yuvarlamayı "potansiyel olarak en akıl sağlığı" seçeneği olarak bırakır; bu, modulo'nun payla (veya sıfır) aynı işaretle bir değer döndürdüğü anlamına gelir.

Not: Ayrıca, çoğu CPU'nun (farkında olduğum tüm CPU'lar) aynı "sıfıra yuvarla" şekilde tamsayı bölme yaptığını da not edeceksiniz. Bu muhtemelen aynı nedenlerden kaynaklanmaktadır.


Ancak kesilen bölümün de kendi tutarsızlıkları vardır: Kırılır (a+b*c)/b == a % bve a >> n == a / 2 ** nbunun için döşeli bölümün aklı başında davranışı vardır.
dan04

İlk örneğiniz mantıklı değil. İkinci örneğiniz programcılar için karışıklıktır: pozitif a ve pozitif n için tutarlıdır, negatif a ve pozitif n için kaydırma sağının nasıl tanımlandığına (aritmetik ve mantıksal) ve negatif n için kırıldığına (ör. 1 >> -2 == a / 2 ** (-2)) Bağlıdır .
Brendan

İlk örnek bir yazım hatasıydı: Yani (a + b * c) % b == a % b, yani %operatör, temettüde bölen-periyodiktir, ki bu genellikle önemlidir. Örneğin, katlanmış bölme ile day_count % 7size haftanın gününü verir, ancak kesilen bölme ile bu, çağdan önceki tarihler için kırılır.
dan04

0

İlk olarak, bir modulo b'nin - b * (div b) değerine eşit olması gerektiğini tekrarlayacağım ve bir dil bunu sağlamazsa, korkunç bir matematiksel karışıklık içinde olursunuz. A - b * (div b) ifadesi aslında kaç uygulamanın bir modulo b hesapladığını ifade eder.

Bazı olası gerekçeler var. Birincisi, maksimum hız istemenizdir, bu nedenle div b, kullanılan işlemcinin sağlayacağı her şey olarak tanımlanır. İşlemcinizin "div" komutu varsa, div komutunun yaptığı her şey div b'dir (tamamen deli olmayan bir şey olduğu sürece).

İkincisi, belirli bir matematiksel davranış istemenizdir. Önce b> 0 olduğunu varsayalım. Div b sonucunun sıfıra yuvarlanmasını istemek oldukça makul. 4 div 5 = 0, 9 div 5 = 1, -4 div 5 = -0 = 0, -9 div 5 = -1. Bu size (-a) div b = - (bir div b) ve (-a) modulo b = - (bir modulo b) verir.

Bu oldukça makul ama mükemmel değil; örneğin (a + b) div b = (bir div b) + 1 tutmaz, diyelim a = -1 ise. Sabit bir b> 0 ile, div b'nin aynı sonucu vereceği şekilde genellikle (b) olası değerler vardır, ancak div b'nin 0'a eşit olduğu 2b - 1 değerleri a -b + 1 ila b-1 arasındadır. Ayrıca, a negatifse bir modulo b'nin negatif olacağı anlamına gelir. Bir modulo b'nin daima 0 ile b-1 arasında bir sayı olmasını isteriz.

Öte yandan, a'nın art arda değerlerinden geçerken, bir modulo b'nin 0'dan b-1'e kadar olan değerlerden geçip tekrar 0 ile başlamasını istemek de mantıklıdır. Ve (a + b) div b'nin (div b) + 1 olmasını talep etmek için, bunu başarmak için, div b sonucunun -finity değerine yuvarlanmasını istiyorsunuz, yani -1 div b = -1. Yine dezavantajları var. (-a) div b = - (bir div b) geçerli değil. Art arda iki veya herhangi bir sayıya bölmek b> 1, sonunda 0 sonucunu vermeyecektir.

Çatışmalar olduğu için, diller hangi avantajların kendileri için daha önemli olduğuna karar vermeli ve buna göre karar vermelidir.

Negatif b için, çoğu insan başını bir div b ve bir modulo b olması gereken şeylerin etrafında bulamaz, bu nedenle basit bir yol, div b = (-a) div (-b) ve b <0 ise bir modulo b = (-a) modulo (-b) veya pozitif b için kod kullanmanın doğal sonucu ne olursa olsun.

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.