Quale logica viene utilizzata quando si programmano i progettisti di linguaggio per decidere quale segnale prende il risultato dell'operazione modulo?

9

Passando attraverso Operazione modulo (la strada che ho inserito esplorando la differenza tra rem e mod ) Mi sono imbattuto in:

In mathematics the result of the modulo operation is the remainder of the Euclidean division. However, other conventions are possible. Computers and calculators have various ways of storing and representing numbers; thus their definition of the modulo operation depends on the programming language and/or the underlying hardware.

Domande:

  • Passando attraverso divisione euclidea ho scoperto che il remainn di questa operazione è sempre positivo (o 0). Quale limite dell'hardware del computer sottostante impone ai programmatori di linguaggi di programmazione di differire dalla matematica?
  • Ogni linguaggio di programmazione ha regole predefinite o non definite secondo le quali il risultato dell'operazione modulo ottiene il suo segno. Quale logica viene adottata mentre si fanno queste regole? E se l'hardware sottostante è il problema, allora le regole non dovrebbero cambiare in base a ciò, indipendentemente dal linguaggio di programmazione?
posta Bleeding Fingers 30.01.2014 - 23:05
fonte

3 risposte

6

L'hardware di tutti i computer moderni è sufficientemente potente per implementare le operazioni mod di entrambi i segnali senza impatto (o banale) sulle prestazioni. Questa non è la ragione.

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 ricomposte in modo affidabile. Questo requisito è esplicito nello standard C ++. Il concetto è strettamente correlato all'indicizzazione di array multidimensionali. L'ho usato spesso.

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

Alcune lingue forniscono una funzione 'rem ()' relativa alla mod e ha qualche altra giustificazione matematica. Non ho mai avuto bisogno di usarlo. Vedi ad esempio frem () in Gnu C. [modificato]

    
risposta data 10.02.2014 - 15:05
fonte
4

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

Con questo in mente, stai davvero chiedendo " quale razionale viene usato quando la programmazione dei progettisti di linguaggi decide come funziona la divisione dei numeri interi? "

Ci sono in realtà circa 7 scelte:

  • arrotondato all'infinito negativo
  • round a infinito positivo
  • round to zero
  • diverse versioni di "round to nearest" (con differenze nel modo in cui qualcosa come 0.5 viene arrotondato)

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

Tutte le opzioni "dal tondo al più vicino" sono un rompicapo per la programmazione, specialmente quando stai facendo qualcosa come le bitmap (ad esempio offset = index / 8; bitNumber = index%8; ).

Questo lascia l'arrotondamento verso zero come la scelta "potenzialmente più sana", il che implica che il modulo restituisca un valore con lo stesso segno del numeratore (o dello zero).

Nota: noterai anche che la maggior parte delle CPU (tutte le CPU di cui sono a conoscenza) eseguono la divisione integer nello stesso modo "round to zero". Questo è probabile che sia per gli stessi motivi.

    
risposta data 10.02.2014 - 19:23
fonte
0

In primo luogo, ripeterò che un modulo b dovrebbe essere uguale a a - b * (a div b), e se una lingua non lo prevede, sei in un pasticcio matematico terribile. Quell'espressione a - b * (a div b) è in realtà il numero di implementazioni che calcolano un modulo b.

Ci sono alcuni possibili razionali. Il primo è che si desidera la massima velocità, quindi un div b è definito come qualunque sia il processore utilizzato. Se il tuo processore ha un'istruzione "div", allora un div b è qualunque cosa l'istruzione div faccia (purché non sia totalmente pazza).

Il secondo è che vuoi un comportamento matematico specifico. Consideriamo innanzitutto b > 0. È abbastanza ragionevole che il risultato di un div b sia arrotondato a 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 fisso b > 0, ci sono di solito (b) possibili valori per un tale che un div b dà lo stesso risultato, tranne che ci sono 2b - 1 valori a da -b + 1 a b-1 dove un div b è uguale a 0. Significa anche che un modulo b sarà negativo se a è negativo. Vorremmo che un modulo b fosse sempre un numero compreso tra 0 e b-1.

D'altra parte, è anche abbastanza ragionevole richiedere che mentre si passa attraverso i valori successivi di a, un modulo b dovrebbe passare attraverso i valori da 0 a b-1 quindi iniziare di nuovo con 0. E per richiedere che (a + b) div b sia (a div b) + 1. Per ottenere ciò, si desidera che il risultato di un div b sia arrotondato verso -infinity, quindi -1 div b = -1. Ancora una volta, ci sono degli svantaggi. (-a) div b = - (a div b) non regge. Ripetutamente dividendo per due o per qualsiasi numero b > 1 alla fine non ti darà un risultato di 0.

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

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

    
risposta data 07.07.2016 - 10:10
fonte