Quale logica viene usata quando i progettisti del linguaggio di programmazione decidono quale segno prende il risultato dell'operazione modulo?


9

Passando attraverso l' operazione Modulo (il viale che ho inserito esplorando la differenza tra rememod ) mi sono imbattuto in:

In matematica il risultato dell'operazione modulo è il resto della divisione euclidea. Tuttavia, sono possibili altre convenzioni. Computer e calcolatrici hanno vari modi di memorizzare e rappresentare numeri; pertanto la loro definizione dell'operazione modulo dipende dal linguaggio di programmazione e / o dall'hardware sottostante.

Domande:

  • Passando attraverso la divisione euclidea ho scoperto che il resto di questa operazione è sempre positivo (o 0). Quale limitazione dell'hardware del computer sottostante costringe i progettisti del linguaggio di programmazione a differire dalla matematica?
  • Ogni linguaggio di programmazione ha una regola predefinita o indefinita in base alla quale il risultato dell'operazione modulo ottiene il suo segno. Quale logica viene adottata mentre si fanno queste regole? E se l'hardware sottostante è la preoccupazione, allora le regole non dovrebbero cambiare in base a ciò, indipendentemente dal linguaggio di programmazione?

1
Nel mio codice ho quasi sempre bisogno di modulo, non del resto. Non ho idea del perché il resto sia così popolare.
CodesInCos

8
Correlati Qual è la differenza? Remainder vs Modulus - Blog di Eric Lippert (di uno dei designer di C #, ma credo che si sia unito al team dopo aver preso questa decisione)
CodesInChaos

1
Se continui a leggere l'articolo di Wikipedia (oltre la parte che hai citato), spiega cosa hai citato abbastanza bene. Che dire di quella spiegazione di cui sei confuso?
Robert Harvey,

1
Una domanda correlata è quale di queste operazioni si associa direttamente alle istruzioni della CPU. In c è definita l'implementazione, che si adatta alla filosofia c di mappare direttamente sull'hardware su quante più piattaforme possibile. Quindi non specifica elementi che potrebbero differire tra le CPU.
CodesInCos

5
@BleedingFingers La programmazione spesso utilizza una divisione di numeri interi che va verso zero, ad es (-3)/2 == -1. Questa definizione può essere utile. Quando vuoi %essere coerente con questa divisione soddisfacente x == (x/y)*y + x % y, finisci con la definizione di %usato in C #.
CodesInChaos

Risposte:


6

L'hardware di tutti i computer moderni è sufficientemente potente per implementare operazioni mod di entrambi i segni senza alcun impatto sulle prestazioni (o banale). Questo non è il motivo.

L'aspettativa comune della maggior parte dei linguaggi informatici è che (a div b) * b + (a mod b) = a. In altre parole, div e mod considerati insieme dividono un numero in parti che possono essere rimesse insieme in modo affidabile. Questo requisito è esplicito nello standard C ++. Il concetto è strettamente correlato all'indicizzazione di array multidimensionali. L'ho usato spesso.

Da ciò si può vedere che div e mod manterranno il segno di a se b è positivo (come di solito lo è).

Alcuni linguaggi forniscono una funzione 'rem ()' correlata a mod e con qualche altra giustificazione matematica. Non ho mai avuto bisogno di usare questo. Vedi ad esempio frem () in Gnu C. [modificato]


Penso che rem(a,b)sia più probabile che sia mod(a,b)se è positivo o mod(a,b) + bse non lo è.
user40989,

3
(a div b) * b + (a mod b) = a- questo, moltissimo. In effetti, contrariamente a come Wikipedia descrive estenderlo ai numeri negativi nella divisione euclidea (in particolare "Il resto è l'unico dei quattro numeri che non può mai essere negativo") mi confonde perché mi è stato sempre insegnato che il resto può essere negativo in ogni classe di matematica a quel livello.
Izkata,

@ user40989: ho detto che non l'ho mai usato. Vedi modifica!
david.pfx,

4

Per la programmazione in genere si desidera X == (X/n)*n + X%n; quindi come viene definito il modulo dipende da come è stata definita la divisione intera.

Con questo in mente, stai davvero chiedendo " Quale logica viene usata quando i progettisti del linguaggio di programmazione decidono come funziona la divisione intera? "

In realtà ci sono circa 7 scelte:

  • tondo all'infinito negativo
  • arrotondare all'infinito positivo
  • arrotondare a zero
  • diverse versioni di "arrotondare al più vicino" (con differenze nel modo in cui qualcosa come 0,5 viene arrotondato)

Adesso considera -( (-X) / n) == X/n. Vorrei che questo fosse vero, poiché qualsiasi altra cosa sembra incoerente (è vero per il virgola mobile) e illogico (una probabile causa di bug e anche un'ottimizzazione potenzialmente mancata). Ciò rende indesiderabili le prime 2 scelte per la divisione di numeri interi (arrotondamento verso l'infinito).

Tutte le scelte "arrotondate al più vicino" sono una seccatura per la programmazione, specialmente quando stai facendo qualcosa come bitmap (ad esempio offset = index / 8; bitNumber = index%8;).

Ciò lascia arrotondare a zero come la scelta "potenzialmente più sana", il che implica che modulo restituisce un valore con lo stesso segno del numeratore (o zero).

Nota: noterai anche che la maggior parte delle CPU (tutte le CPU di cui sono a conoscenza) eseguono la divisione intera nello stesso modo "arrotondato a zero". È probabile che ciò avvenga per gli stessi motivi.


Ma troncare la divisione ha anche le sue incoerenze: si rompe (a+b*c)/b == a % be a >> n == a / 2 ** n, per cui la divisione pavimentata ha un comportamento sano.
dan04,

Il tuo primo esempio non ha senso. Il tuo secondo esempio è un disastro per i programmatori: per positivo ae positivo n è coerente, per negativo ae positivo n dipende da come viene definito shift destro (aritmetico vs. logico) e per negativo n è rotto (es 1 >> -2 == a / 2 ** (-2).).
Brendan,

Il primo esempio è stato un refuso: intendevo (a + b * c) % b == a % bdire che l' %operatore è un divisore periodico del dividendo, che è spesso importante. Ad esempio, con la divisione pavimentata, day_count % 7ti dà il giorno della settimana, ma con la divisione troncata, questo si interrompe per le date prima dell'epoca.
dan04,

0

Per prima cosa, ripeterò che a modulo b dovrebbe essere uguale a a - b * (a div b), e se una lingua non lo fornisce, ci si trova in un disastro matematico terribile. Quell'espressione a - b * (a div b) è in realtà quante implementazioni calcolano un modulo b.

Ci sono alcune possibili motivazioni. Il primo è che si desidera la massima velocità, quindi un div b è definito come qualunque cosa il processore utilizzato fornirà. Se il tuo processore ha un'istruzione "div", allora una div b è qualunque cosa faccia quell'istruzione div (purché sia ​​qualcosa di non completamente folle).

Il secondo è che si desidera un comportamento matematico specifico. Supponiamo innanzitutto che b> 0. È abbastanza ragionevole desiderare che il risultato di un div b venga arrotondato verso zero. Quindi 4 div 5 = 0, 9 div 5 = 1, -4 div 5 = -0 = 0, -9 div 5 = -1. Questo ti dà (-a) div b = - (a div b) e (-a) modulo b = - (a modulo b).

Questo è abbastanza ragionevole ma non perfetto; per esempio (a + b) div b = (a div b) + 1 non regge, diciamo se a = -1. Con un b> 0 fisso, di solito ci sono (b) possibili valori per a tale che un div b dia lo stesso risultato, tranne che ci sono 2b - 1 valori a da -b + 1 a b-1 dove a div b è uguale a 0 Significa anche che un modulo b sarà negativo se a è negativo. Vorremmo che un modulo b sia sempre un numero nell'intervallo compreso tra 0 e b-1.

D'altra parte, è anche abbastanza ragionevole richiedere che mentre passi attraverso i valori successivi di a, a modulo b dovrebbe passare attraverso i valori da 0 a b-1 e ricominciare da 0. E per richiedere che (a + b) div b sia (a div b) + 1. Per ottenere ciò, vuoi che il risultato di un div b sia arrotondato verso -infinito, quindi -1 div b = -1. Ancora una volta, ci sono degli svantaggi. (-a) div b = - (a div b) non regge. Dividendo ripetutamente per due o per qualsiasi numero b> 1 alla fine non otterrai un risultato pari a 0.

Poiché ci sono conflitti, le lingue dovranno decidere quale serie di vantaggi è più importante per loro e decidere di conseguenza.

Per b negativo, la maggior parte delle persone non riesce a capire cosa dovrebbero essere in primo luogo un div b e un modulo b, quindi un modo semplice è definire che un div b = (-a) div (-b) e a modulo b = (-a) modulo (-b) se b <0, o qualunque sia l'esito naturale dell'utilizzo del codice per positivo b.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.