2013-02-09 5 views
10

Per qualsiasi numero intero ingresso W limitata dalla gamma R = [x, y], il "troppo pieno", per mancanza di un termine migliore, di W sopra R è W % (y-x+1) + x . Ciò causa che si riavvolge se W supera y.Esiste un'espressione che utilizza modulo per eseguire il wrap-around all'indietro ("overflow inverso")?

Come esempio di questo principio, supponiamo iteriamo nel corso di mesi di un calendario:

int this_month = 5; 
int next_month = (this_month + 1) % 12; 

dove entrambi interi sarà compreso tra 0 e 11, inclusi. Pertanto, l'espressione sopra "blocca" l'intero nell'intervallo R = [0,11]. Questo approccio all'uso di un'espressione è semplice, elegante e vantaggioso in quanto omette la diramazione.

Ora, cosa succede se vogliamo fare la stessa cosa, ma all'indietro? La seguente espressione funziona:

int last_month = ((this_month - 1) % 12 + 12) % 12; 

ma è astruso. Come può essere abbellito?


tl; dr - Può l'espressione ((x-1) % k + k) % k essere ulteriormente semplificata?

Nota: tag C++ specificato perché altri linguaggi gestiscono gli operandi negativi per l'operatore modulo in modo diverso.

risposta

9

tuo espressione dovrebbe essere ((x-1) + k) % k. Questo avvolgerà correttamente x = 0 intorno a 11. In generale, se vuoi fare un passo indietro di più di 1, devi assicurarti di aggiungere abbastanza in modo che il primo operando dell'operazione di modulo sia> = 0.

Ecco un'implementazione in Python.Si dovrebbe essere in grado di trasferirla a lingua di vostra scelta:

def wrap_around(value, delta, min_val, max_val): 
    if delta >= 0: 
     return (value + delta - min_val) % max_val + min_val 
    else: 
     return ((value + delta) + max_val * (-delta) - min_val) % max_val + min_val 

Questo permette anche di utilizzare mesi etichettati 0-11 o 1-12, impostazione min_val e max_val conseguenza.

+3

'((x-1) + k)% k' è la soluzione! – CpILL

+0

Il numero '-1' non può essere inferiore a' - (k-1) ' –

7

k% k sarà sempre 0. Io non sono al 100% sicuro di quello che stai cercando di fare, ma sembra che si desidera che il mese scorso per essere bloccato tra 0 e 11 inclusi.

(this_month + 11) % 12 

Dovrebbe essere sufficiente.

+3

In realtà, '-1% 12 == -1'. – Mankarse

+0

'-1% 12 = 11' - è così in' C++ '? –

+0

@ Mankarse22 lo fa? L'ho appena provato nella mia calcolatrice, non ho avuto la possibilità di provarlo in un compilatore. – Enfyve

4

La soluzione generale è quello di scrivere una funzione che calcola il valore che si desidera:

//Returns floor(a/n) (with the division done exactly). 
//Let ÷ be mathematical division, and/be C++ division. 
//We know 
// a÷b = a/b + f (f is the remainder, not all 
//     divisions have exact Integral results) 
//and 
// (a/b)*b + a%b == a (from the standard). 
//Together, these imply (through algebraic manipulation): 
// sign(f) == sign(a%b)*sign(b) 
//We want the remainder (f) to always be >=0 (by definition of flooredDivision), 
//so when sign(f) < 0, we subtract 1 from a/n to make f > 0. 
template<typename Integral> 
Integral flooredDivision(Integral a, Integral n) { 
    Integral q(a/n); 
    if ((a%n < 0 && n > 0) || (a%n > 0 && n < 0)) --q; 
    return q; 
} 

//flooredModulo: Modulo function for use in the construction 
//looping topologies. The result will always be between 0 and the 
//denominator, and will loop in a natural fashion (rather than swapping 
//the looping direction over the zero point (as in C++11), 
//or being unspecified (as in earlier C++)). 
//Returns x such that: 
// 
//Real a = Real(numerator) 
//Real n = Real(denominator) 
//Real r = a - n*floor(n/d) 
//x = Integral(r) 
template<typename Integral> 
Integral flooredModulo(Integral a, Integral n) { 
    return a - n * flooredDivision(a, n); 
} 
1

Non sono sicuro se si dovesse avere lo stesso problema come me, ma il mio problema era essenzialmente che volevo per vincolare tutti numeri in un determinato intervallo. Supponiamo che l'intervallo sia compreso tra 0 e 6, quindi l'utilizzo di% 7 significa che qualsiasi numero superiore a 6 tornerà a 0 o sopra. Il problema reale è che i numeri meno di zero non avvolgere di nuovo intorno al 6. Ho una soluzione a questo (dove X è il limite superiore del range numero e 0 è il minimo):

if(inputNumber <0)//If this is a negative number 
{ 
(X-(inputNumber*-1))%X; 
} 
else 
{ 
inputNumber%X; 
} 
2

Easy Peasy, non utilizzare il primo operatore del modulo, è superfluo:

int last_month = (this_month - 1 + 12) % 12; 

che è il caso generale

In questo caso è possibile scrivere 11, ma vorrei ancora fare la -1 + 11 come afferma più chiaramente ciò che vuoi ottenere.

Problemi correlati