Il modo migliore per fare in modo che il modulo di Java si comporti come dovrebbe con i numeri negativi?


103

In Java quando lo fai

a % b

Se a è negativo, restituirà un risultato negativo, invece di avvolgere in b come dovrebbe. Qual è il modo migliore per risolvere questo problema? L'unico modo in cui posso pensare è

a < 0 ? b + a : a % b

12
Non esiste un comportamento "corretto" del modulo quando si tratta di numeri negativi - molte lingue lo fanno in questo modo, molte lo fanno in modo diverso e alcune lingue fanno qualcosa di completamente diverso. Almeno i primi due hanno i loro pro e contro.

4
questo è solo strano per me. ho pensato che dovrebbe restituire negativo solo se b è negativo.
fent


2
è. ma il titolo di quella domanda dovrebbe essere rinominato. Non farei clic su questa domanda se cercassi questo perché so già come funziona il modulo java.
fent

4
L'ho appena rinominato in quello da "Perché -13% 64 = 51?", Che non sarebbe mai stato in un milione di anni qualcosa su cui qualcuno avrebbe cercato. Quindi questo titolo della domanda è molto migliore e molto più ricercabile su parole chiave come modulo, negativo, calcolo, numeri.
Erick Robertson

Risposte:


144

Si comporta come dovrebbe a% b = a - a / b * b; cioè è il resto.

Puoi fare (a% b + b)% b


Questa espressione funziona in quanto il risultato di (a % b)è necessariamente inferiore a b, non importa se aè positivo o negativo. L'aggiunta si bprende cura dei valori negativi di a, poiché (a % b)è un valore negativo tra -be 0, (a % b + b)è necessariamente inferiore a be positivo. L'ultimo modulo è presente nel caso in cui all'inizio afosse positivo, poiché se afosse positivo (a % b + b)diventerebbe maggiore di b. Pertanto, lo (a % b + b) % btrasforma in più piccolo di bnuovo (e non influisce sui avalori negativi ).


3
funziona meglio grazie. e funziona anche per i numeri negativi che sono molto più grandi di b.
fent

6
Funziona poiché il risultato di (a % b)è necessariamente inferiore a b(non importa se aè positivo o negativo), l'aggiunta si bprende cura dei valori negativi di a, poiché (a % b)è inferiore be inferiore a 0, (a % b + b)è necessariamente inferiore a be positivo. L'ultimo modulo è presente nel caso in cui all'inizio afosse positivo, poiché se afosse positivo (a % b + b)diventerebbe maggiore di b. Pertanto, lo (a % b + b) % btrasforma in più piccolo di bnuovo (e non influisce sui avalori negativi ).
ethanfar

1
@eitanfar ho incluso la tua eccellente spiegazione nella risposta (con una piccola correzione per a < 0, forse potresti dare un'occhiata)
Maarten Bodewes

5
Ho appena visto questo commentato su un'altra domanda riguardante lo stesso argomento; Potrebbe valere la pena ricordare che si (a % b + b) % binterrompe per valori molto grandi di ae b. Ad esempio, l'utilizzo di a = Integer.MAX_VALUE - 1e b = Integer.MAX_VALUEdarà -3come risultato, che è un numero negativo, che è ciò che si voleva evitare.
Thorbear

2
@Mikepote usando a whilesarebbe più lento se ne hai davvero bisogno tranne che hai solo bisogno di un, ifnel qual caso è effettivamente più veloce.
Peter Lawrey

92

A partire da Java 8, puoi usare Math.floorMod (int x, int y) e Math.floorMod (long x, long y) . Entrambi questi metodi restituiscono gli stessi risultati della risposta di Peter.

Math.floorMod( 2,  3) =  2
Math.floorMod(-2,  3) =  1
Math.floorMod( 2, -3) = -1
Math.floorMod(-2, -3) = -2

1
migliore risposta per Java 8+
Charney Kaye

Fantastico, non lo sapevo. Java 8 ha risolto definitivamente alcuni PITA.
Franz D.

4
Buon modo. Ma sfortunatamente non funziona con floato doubleargomenti. L'operatore binario Mod ( %) funziona anche con gli operandi floate double.
Mir-Ismaili

11

Per coloro che non usano ancora (o non sono in grado di usare) Java 8, Guava è venuto in soccorso con IntMath.mod () , disponibile da Guava 11.0.

IntMath.mod( 2, 3) = 2
IntMath.mod(-2, 3) = 1

Un avvertimento: a differenza di Math.floorMod () di Java 8, il divisore (il secondo parametro) non può essere negativo.


7

Nella teoria dei numeri, il risultato è sempre positivo. Immagino che non sia sempre così nei linguaggi dei computer perché non tutti i programmatori sono matematici. I miei due centesimi, lo considererei un difetto di progettazione del linguaggio, ma non puoi cambiarlo ora.

= MOD (-4,180) = 176 = MOD (176, 180) = 176

perché 180 * (-1) + 176 = -4 equivale a 180 * 0 + 176 = 176

Usando l'esempio dell'orologio qui, http://mathworld.wolfram.com/Congruence.html non diresti duration_of_time mod cycle_length è -45 minuti, diresti 15 minuti, anche se entrambe le risposte soddisfano l'equazione di base.


1
Nella teoria dei numeri non è sempre positivo ... Cadono in classi di congruenza. Sei libero di scegliere qualsiasi candidato da quella classe per i tuoi scopi di notazione, ma l'idea è che si associ a tutta quella classe, e se l'utilizzo di un altro candidato specifico da esso rende un certo problema significativamente più semplice (scegliendo -1invece di n-1per esempio) allora fallo.
BeUndead

2

Java 8 lo ha Math.floorMod, ma è molto lento (la sua implementazione ha più divisioni, moltiplicazioni e un condizionale). È possibile che la JVM abbia uno stub ottimizzato intrinseco, tuttavia, che lo velocizzerebbe in modo significativo.

Il modo più veloce per farlo senza floorModè come alcune altre risposte qui, ma senza rami condizionali e solo una operazione lenta %.

Supponendo che n sia positivo e x possa essere qualsiasi cosa:

int remainder = (x % n); // may be negative if x is negative
//if remainder is negative, adds n, otherwise adds 0
return ((remainder >> 31) & n) + remainder;

I risultati quando n = 3:

x | result
----------
-4| 2
-3| 0
-2| 1
-1| 2
 0| 0
 1| 1
 2| 2
 3| 0
 4| 1

Se hai solo bisogno di una distribuzione uniforme tra 0e n-1e non l'esatto operatore mod, e il tuo xnon si raggruppa vicino 0, il seguente sarà ancora più veloce, poiché c'è più parallelismo a livello di istruzione e il %calcolo lento avverrà in parallelo con l'altro parti in quanto non dipendono dal suo risultato.

return ((x >> 31) & (n - 1)) + (x % n)

I risultati per quanto sopra con n = 3:

x | result
----------
-5| 0
-4| 1
-3| 2
-2| 0
-1| 1
 0| 0
 1| 1
 2| 2
 3| 0
 4| 1
 5| 2

Se l'input è casuale nell'intero intervallo di un int, la distribuzione di entrambe le due soluzioni sarà la stessa. Se i cluster di input sono vicini allo zero, ci saranno troppi pochi risultati n - 1in quest'ultima soluzione.


1

Ecco un'alternativa:

a < 0 ? b-1 - (-a-1) % b : a % b

Questo potrebbe o non potrebbe essere più veloce dell'altra formula [(a% b + b)% b]. A differenza dell'altra formula, contiene un ramo, ma utilizza un'operazione modulo in meno. Probabilmente una vittoria se il computer può prevedere correttamente uno <0.

(Modifica: corretta la formula.)


1
Ma l'operazione modulo richiede una divisione che potrebbe essere anche più lenta (soprattutto se il processore indovina il ramo correttamente quasi tutto il tempo). Quindi questo è forse meglio.
dave

@KarstenR. Hai ragione! Ho corretto la formula, ora funziona bene (ma necessita di altre due sottrazioni).
Stefan Reich

Questo è vero @dave
Stefan Reich
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.