Come codificare un operatore modulo (%) in C / C ++ / Obj-C che gestisce i numeri negativi


87

Uno dei miei preferiti per i linguaggi derivati ​​dal C (come matematico) è questo

(-1) % 8 // comes out as -1, and not 7

fmodf(-1,8) // fails similarly

Qual è la soluzione migliore?

Il C ++ consente la possibilità di modelli e sovraccarico dell'operatore, ma entrambi sono acque torbide per me. esempi ricevuti con gratitudine.


1
Non penso che questo sia un "duplicato" di stackoverflow.com/questions/828092/… secondo la definizione ufficiale. Non è vero che le risposte di questa domanda possono essere unite a quella, perché questa domanda chiede solo il modulo, non anche la divisione. Ma penso che questa domanda sia coperta da quella, quindi è vicina. La mia risposta è già lì, FWIW.
Steve Jessop

Forse quel filo dovrebbe essere diviso, poiché pone due domande separate. il modo migliore per farlo potrebbe essere ripetere la domanda di divisione separatamente e poi indirizzarla verso quella risposta. Lo lascio a qualcuno che capisce meglio i meccanismi di questo sito.
P i

3
@Pi owhere si %dice che sia il modulo ... è il resto .
obataku

1
Ecco un altro thread che questo è un "duplicato" di: stackoverflow.com/questions/1082917/… Solo per riferimento su questo %problema.
leetNightshade

Se stai dividendo solo i poteri di due, potrebbe essere un'idea migliore usare e:(-1) & 8 == 7
Henricus V.

Risposte:


75

Prima di tutto vorrei sottolineare che non puoi nemmeno fare affidamento sul fatto che (-1) % 8 == -1. l'unica cosa su cui puoi fare affidamento è quella (x / y) * y + ( x % y) == x. Tuttavia, se il resto è negativo o meno è definito dall'implementazione .

Ora perché usare i modelli qui? Andrebbe bene un sovraccarico per int e long.

int mod (int a, int b)
{
   int ret = a % b;
   if(ret < 0)
     ret+=b;
   return ret;
}

e ora puoi chiamarlo come mod (-1,8) e sembrerà essere 7.

Modifica: ho trovato un bug nel mio codice. Non funzionerà se b è negativo. Quindi penso che questo sia meglio:

int mod (int a, int b)
{
   if(b < 0) //you can check for b == 0 separately and do what you want
     return -mod(-a, -b);   
   int ret = a % b;
   if(ret < 0)
     ret+=b;
   return ret;
}

Riferimento: C ++ 03 paragrafo 5.6 clausola 4:

L'operatore binario / restituisce il quoziente e l'operatore binario% restituisce il resto dalla divisione della prima espressione per la seconda. Se il secondo operando di / o% è zero il comportamento è indefinito; altrimenti (a / b) * b + a% b è uguale ad a. Se entrambi gli operandi sono non negativi, il resto non è negativo; in caso contrario, il segno del resto è definito dall'implementazione .


2
@ Ohmu: Sì, è nello standard C ++. <quote> Per gli operandi integrali l'operatore / restituisce il quoziente algebrico con qualsiasi parte frazionaria scartata; se il quoziente a / b è rappresentabile nel tipo di risultato, (a / b) * b + a% b è uguale ad a. </quote>
Ben Voigt

5
-1. Sono passati 11 anni da quando è stata definita l'implementazione. ISO 9899: 1999 lo ha definito e sfortunatamente ha scelto la definizione sbagliata.
R .. GitHub SMETTA DI AIUTARE IL GHIACCIO

3
@Armen: hai convenientemente cancellato la nota a piè di pagina <quote> ... la divisione intera segue le regole definite nello standard ISO Fortran, ISO / IEC 1539: 1991, in cui il quoziente è sempre arrotondato verso zero </quote>. Il nuovo standard C ++ aggiorna questo comportamento da "preferito" a obbligatorio, proprio come Fortran e C.
Ben Voigt

2
@Armen: la vecchia specifica è rotta, ma la rottura è diversa dal problema del segno ed è facile non vederla finché non si guarda la nuova formulazione. C ++ 03 non aveva "se il quoziente a / b è rappresentabile nel tipo del risultato", il che causa problemi per INT_MIN / -1(su implementazioni in complemento a due). Con la vecchia specifica, -32768 % -1potrebbe essere necessario valutare -65536(che non è nemmeno nell'intervallo del tipo a 16 bit, bleah!) Affinché l'identità possa essere mantenuta.
Ben Voigt

1
re "Comunque se il resto è negativo è definito dall'implementazione.", C ++ 11 garantisce che la divisione intera arrotonda verso 0.
Saluti e hth. - Alf

13

Ecco una funzione C che gestisce valori interi OR negativi o frazionari per ENTRAMBI GLI OPERANDI

#include <math.h>
float mod(float a, float N) {return a - N*floor(a/N);} //return in range [0, N)

Questa è sicuramente la soluzione più elegante dal punto di vista matematico. Tuttavia, non sono sicuro che sia robusto nella gestione degli interi. A volte gli errori in virgola mobile si insinuano durante la conversione di int -> fp -> int.

Sto usando questo codice per non int e una funzione separata per int.

NOTA: è necessario intercettare N = 0!

Codice tester:

#include <math.h>
#include <stdio.h>

float mod(float a, float N)
{
    float ret = a - N * floor (a / N);

    printf("%f.1 mod %f.1 = %f.1 \n", a, N, ret);

    return ret;
}

int main (char* argc, char** argv)
{
    printf ("fmodf(-10.2, 2.0) = %f.1  == FAIL! \n\n", fmodf(-10.2, 2.0));

    float x;
    x = mod(10.2f, 2.0f);
    x = mod(10.2f, -2.0f);
    x = mod(-10.2f, 2.0f);
    x = mod(-10.2f, -2.0f);

    return 0;
}

(Nota: puoi compilarlo ed eseguirlo direttamente da CodePad: http://codepad.org/UOgEqAMA )

Produzione:

fmodf (-10.2, 2.0) = -0.20 == FAIL!

10.2 mod 2.0 = 0.2
10.2 mod -2.0 = -1.8
-10.2 mod 2.0 = 1.8
-10.2 mod -2.0 = -0.2


Sfortunatamente, questo non funziona con i numeri interi. Dovrebbero essere convertiti in virgola mobile prima della divisione per consentirti di utilizzarli floor(). Inoltre, potresti perdere precisione quando converti in float: prova (float)1000000001/3, rimarrai sorpreso dai risultati!
cmaster - ripristina monica l'

9

Ho appena notato che Bjarne Stroustrup etichetta %come operatore resto , non operatore modulo.

Scommetto che questo è il suo nome formale nelle specifiche ANSI C e C ++, e che l'abuso di terminologia si è insinuato. Qualcuno lo sa per certo?

Ma se questo è il caso, la funzione fmodf () di C (e probabilmente altre) sono molto fuorvianti. dovrebbero essere etichettati fremf (), ecc


1
Lo standard C11 (o la bozza pubblica finale per l'esattezza) menziona sei volte "modulo", ma solo in relazione alla rappresentazione di vario tipo. Non menziona nemmeno una volta "modulo" in relazione all'operatore resto ( %).
Nisse Engström

7

La funzione generale più semplice per trovare il modulo positivo sarebbe questa: funzionerebbe su valori sia positivi che negativi di x.

int modulo(int x,int N){
    return (x % N + N) %N;
}

6

Per i numeri interi questo è semplice. Basta fare

(((x < 0) ? ((x % N) + N) : x) % N)

dove suppongo che Nsia positivo e rappresentabile nel tipo di x. Il tuo compilatore preferito dovrebbe essere in grado di ottimizzarlo, in modo tale che finisca in una sola operazione di mod in assembler.


3
Non funziona: int x=-9001; unsigned int N=2000;perché dà 2295, non 999.
Hubert Kario

1
@HubertKario Forse controllare di nuovo? Non è possibile che qualcosa modulo 2000 dia 2295, devi aver commesso un errore.
sam hocevar

2
@SamHocevar: Penso che il problema qui siano le strane regole di promozione del numero intero C. firmato promuove a unsigned e promuove un valore intero con segno negativo a unsigned richiama un comportamento indefinito in C.
datenwolf

1
Credo che una forma molto più semplice (e più efficiente) sarebbe: (x < 0) ? (x % N + N) : (x % N).
Chris Nolet

3

La migliore soluzione ¹per un matematico è usare Python.

Il sovraccarico degli operatori C ++ ha poco a che fare con questo. Non è possibile sovraccaricare gli operatori per i tipi incorporati. Quello che vuoi è semplicemente una funzione. Ovviamente puoi usare i modelli C ++ per implementare quella funzione per tutti i tipi rilevanti con solo 1 pezzo di codice.

La libreria C standard fornisce fmod, se ricordo correttamente il nome, i tipi in virgola mobile.

Per i numeri interi è possibile definire un modello di funzione C ++ che restituisca sempre resto non negativo (corrispondente alla divisione euclidea) come ...

#include <stdlib.h>  // abs

template< class Integer >
auto mod( Integer a, Integer b )
    -> Integer
{
    Integer const r = a%b;
    return (r < 0? r + abs( b ) : r);
}

... e scrivi mod(a, b)invece di a%b.

Qui il tipo Integerdeve essere un tipo intero con segno.

Se vuoi il comportamento matematico comune in cui il segno del resto è lo stesso del segno del divisore, puoi fare ad es.

template< class Integer >
auto floor_div( Integer const a, Integer const b )
    -> Integer
{
    bool const a_is_negative = (a < 0);
    bool const b_is_negative = (b < 0);
    bool const change_sign  = (a_is_negative != b_is_negative);

    Integer const abs_b         = abs( b );
    Integer const abs_a_plus    = abs( a ) + (change_sign? abs_b - 1 : 0);

    Integer const quot = abs_a_plus / abs_b;
    return (change_sign? -quot : quot);
}

template< class Integer >
auto floor_mod( Integer const a, Integer const b )
    -> Integer
{ return a - b*floor_div( a, b ); }

... con lo stesso vincolo Integer, che è un tipo con segno.


¹ Perché la divisione intera di Python arrotonda verso l'infinito negativo.


il tuo codice sembra avere lo stesso bug del mio prima della mia modifica. E se b è negativo? :)
Armen Tsirunyan

1
@Armen: grazie! ma sono troppo pigro per modificare solo per questo ... :-)
Saluti e hth. - Alf

@ArmenTsirunyan: il rrisultato deve rendere a= r + b*(a/b)true. indipendentemente da come viene implementata la divisione intera, b*somethingè un multiplo di b. questo rende run risultato modulo valido anche se negativo. puoi aggiungere abs ( b) e sarà comunque un risultato modulo valido.
Saluti e salute. - Alf

2
@downvoters: questa risposta è ancora corretta, mentre la "soluzione" selezionata ora contiene commenti errati a causa delle nuove garanzie in C ++ 11. È dannatamente ironico votare una risposta che è ancora corretta. Senza motivo, si deve presumere che almeno 2 persone associative, con un grado quasi assoluto di ignoranza, leggano il commento di questa domanda e hanno votato in modo impulsivo in modo associativo. Per favore, spiega i tuoi voti negativi.
Saluti e salute. - Alf

1
Il risultato matematicamente desiderato è che il resto sia zero o che abbia lo stesso segno del divisore (denominatore). Se il divisore è negativo, il resto dovrebbe essere zero o negativo. L'implementazione C / C ++ fa sì che il resto sia zero o che abbia lo stesso segno del dividendo (numeratore).
rcgldr

2

Oh, odio% design anche per questo ...

Puoi convertire i dividendi in non firmati in un modo come:

unsigned int offset = (-INT_MIN) - (-INT_MIN)%divider

result = (offset + dividend) % divider

dove offset è il più vicino al multiplo (-INT_MIN) del modulo, quindi l'aggiunta e la sottrazione non cambierà il modulo. Nota che ha un tipo senza segno e il risultato sarà intero. Sfortunatamente non può convertire correttamente i valori INT_MIN ... (- offset-1) in quanto causano overflow arifmetico. Ma questo metodo ha il vantaggio di una singola aritmetica aggiuntiva per operazione (e non condizionali) quando si lavora con un divisore costante, quindi è utilizzabile in applicazioni simili a DSP.

C'è un caso speciale, dove il divisore è 2 N (potenza intera di due), per il quale il modulo può essere calcolato usando una semplice logica aritmetica e

dividend&(divider-1)

per esempio

x mod 2 = x & 1
x mod 4 = x & 3
x mod 8 = x & 7
x mod 16 = x & 15

Il modo più comune e meno complicato è ottenere il modulo utilizzando questa funzione (funziona solo con divisore positivo):

int mod(int x, int y) {
    int r = x%y;
    return r<0?r+y:r;
}

Questo solo risultato corretto se è negativo.

Inoltre potresti ingannare:

(p% q + q)% q

È molto breve ma usa due% che sono comunemente lenti.


2

Credo che un'altra soluzione a questo problema sarebbe l'uso di variabili di tipo long invece di int.

Stavo solo lavorando su un codice in cui l'operatore% restituiva un valore negativo che ha causato alcuni problemi (per la generazione di variabili casuali uniformi su [0,1] non vuoi davvero numeri negativi :)), ma dopo aver cambiato le variabili a tipo lungo, tutto stava funzionando senza intoppi ei risultati corrispondevano a quelli che stavo ottenendo eseguendo lo stesso codice in python (importante per me perché volevo essere in grado di generare gli stessi numeri "casuali" su più piattaforme.


2

Ecco una nuova risposta a una vecchia domanda, basata su questo documento di Microsoft Research e sui riferimenti in esso contenuti.

Si noti che da C11 e C ++ 11 in poi, la semantica di divè diventata troncamento verso zero (vedere [expr.mul]/4). Inoltre, per Ddiviso per d, C ++ 11 garantisce quanto segue sul quoziente qTe sul restorT

auto const qT = D / d;
auto const rT = D % d;
assert(D == d * qT + rT);
assert(abs(rT) < abs(d));
assert(signum(rT) == signum(D));

dove viene signummappato a -1, 0, +1, a seconda che il suo argomento sia <, ==,> o 0 (vedere questa domanda e risposta per il codice sorgente).

Con divisione troncata, il segno del resto è uguale al segno del dividendoD , cioè -1 % 8 == -1. C ++ 11 fornisce anche una std::divfunzione che restituisce una struttura con membri quote in rembase alla divisione troncata.

Sono possibili altre definizioni, ad esempio la cosiddetta divisione floored può essere definita in termini di divisione troncata incorporata

auto const I = signum(rT) == -signum(d) ? 1 : 0;
auto const qF = qT - I;
auto const rF = rT + I * d;
assert(D == d * qF + rF);
assert(abs(rF) < abs(d));
assert(signum(rF) == signum(d));

Con la divisione in piani, il segno del resto è uguale al segno del divisored . In lingue come Haskell e Oberon, ci sono operatori incorporati per la divisione floored. In C ++, dovresti scrivere una funzione utilizzando le definizioni di cui sopra.

Ancora un altro modo è la divisione euclidea , che può anche essere definita in termini di divisione troncata incorporata

auto const I = rT >= 0 ? 0 : (d > 0 ? 1 : -1);
auto const qE = qT - I;
auto const rE = rT + I * d;
assert(D == d * qE + rE);
assert(abs(rE) < abs(d));
assert(signum(rE) != -1);

Con la divisione euclidea, il segno del resto è sempre positivo .


2

Per una soluzione che non utilizza rami e solo 1 mod, puoi fare quanto segue

// Works for other sizes too,
// assuming you change 63 to the appropriate value
int64_t mod(int64_t x, int64_t div) {
  return (x % div) + (((x >> 63) ^ (div >> 63)) & div);
}

1
/ * Attenzione: la macro mod valuta più volte gli effetti collaterali dei suoi argomenti. * /
#define mod (r, m) (((r)% (m)) + ((r) <0)? (m): 0)

... o semplicemente abituati a trovare un rappresentante per la classe di equivalenza.


2
"Abituarsi a trovare un rappresentante per la classe di equivalenza" ?! È una sciocchezza. Se lo desideri, puoi semplicemente utilizzare il "rappresentante" originale r. L' %operatore non ha nulla a che fare con le classi di equivalenza. È l'operatore resto e il resto è ben definito algebricamente come non negativo e inferiore al divisore. Purtroppo C lo ha definito nel modo sbagliato. Tuttavia, +1 per avere una delle migliori risposte.
R .. GitHub SMETTA DI AIUTARE IL GHIACCIO

0

Modello di esempio per C ++

template< class T >
T mod( T a, T b )
{
    T const r = a%b;
    return ((r!=0)&&((r^b)<0) ? r + b : r);
}

Con questo modello, il resto restituito sarà zero o avrà lo stesso segno del divisore (denominatore) (l'equivalente dell'arrotondamento verso l'infinito negativo), invece del comportamento C ++ del resto pari a zero o con lo stesso segno del dividendo ( numeratore) (l'equivalente dell'arrotondamento verso lo zero).



-1
unsigned mod(int a, unsigned b) {
    return (a >= 0 ? a % b : b - (-a) % b);
}

-1

Questa soluzione (da usare quando modè positiva) evita di prendere tutte le operazioni negative di divisione o resto:

int core_modulus(int val, int mod)
{
    if(val>=0)
        return val % mod;
    else
        return val + mod * ((mod - val - 1)/mod);
}

-2

Farei:

((-1)+8) % 8 

Questo aggiunge l'ultimo numero al primo prima di eseguire il modulo dando 7 come desiderato. Questo dovrebbe funzionare per qualsiasi numero fino a -8. Per -9 aggiungi 2 * 8.


2
E per una variabile il cui valore potrebbe essere -99999?
Keith Thompson
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.