web-dev-qa-db-fra.com

Comment coder un opérateur modulo (%) en C / C ++ / Obj-C qui gère les nombres négatifs

L’un de mes ennemis des langages dérivés du C (en tant que mathématicien) est le suivant:

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

fmodf(-1,8) // fails similarly

Quelle est la meilleure solution?

C++ permet la surcharge de modèles et d'opérateurs, mais les deux sont des eaux troubles pour moi. exemples reçus avec gratitude.

79
P i

Tout d’abord, je voudrais souligner que vous ne pouvez même pas compter sur le fait que (-1) % 8 == -1. la seule chose sur laquelle vous pouvez compter est que (x / y) * y + ( x % y) == x. Toutefois, le fait que le reste soit négatif ou non est défini par la mise en oeuvre .

Maintenant, pourquoi utiliser des modèles ici? Une surcharge pour ints et long ferait l'affaire.

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

et maintenant vous pouvez l'appeler comme mod (-1,8) et il semblera être 7.

Edit: j'ai trouvé un bug dans mon code. Cela ne fonctionnera pas si b est négatif. Donc je pense que c'est mieux:

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;
}

Référence: C++ 03, paragraphe 5.6, clause 4:

L'opérateur binaire/donne le quotient et l'opérateur% binaire donne le reste de la division de la première expression par la seconde. Si le deuxième opérande de/ou% est zéro, le comportement est indéfini; sinon (a/b) * b + a% b est égal à a. Si les deux opérandes sont non négatifs, le reste est non négatif. sinon, le signe du reste est défini par l'implémentation .

72
Armen Tsirunyan

Voici une fonction C qui gère positif OR entier négatif OR valeurs fractionnaires pour BOTH OPERANDS

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

C’est sûrement la solution la plus élégante du point de vue mathématique. Cependant, je ne suis pas sûr que ce soit robuste dans la gestion des entiers. Parfois, des erreurs en virgule flottante se glissent lors de la conversion int -> fp -> int.

J'utilise ce code pour les non-int, et une fonction distincte pour int.

NOTE: besoin de piéger N = 0!

Code du testeur:

#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;
}

(Remarque: vous pouvez le compiler et le lancer directement à partir de CodePad: http://codepad.org/UOgEqAMA )

Sortie:

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

11
P i

Je viens de remarquer que les étiquettes de Bjarne Stroustrup % comme opérateur reste, pas l'opérateur modulo.

Je parierais que c'est son nom officiel dans les spécifications ANSI C & C++, et qu'un abus de terminologie s'est introduit. Est-ce que quelqu'un le sait déjà?

Mais si tel est le cas, alors la fonction fmodf () de C (et probablement d'autres) est très trompeuse. ils devraient être étiquetés fremf (), etc

7
P i

Pour les entiers, c'est simple. Il suffit de faire

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

où je suppose que N est positif et représentable dans le type de x. Votre compilateur préféré devrait pouvoir optimiser cette sortie, de sorte qu’elle aboutisse à une seule opération de modification dans l’assembleur.

5
Jens Gustedt

La fonction générale la plus simple pour trouver le modulo positif serait la suivante: elle fonctionnerait à la fois sur les valeurs positives et négatives de x.

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

La meilleure solution ¹ pour un mathématicien consiste à utiliser Python.

La surcharge d'opérateurs C++ a peu à voir avec cela. Vous ne pouvez pas surcharger les opérateurs pour les types intégrés. Ce que vous voulez est simplement une fonction. Bien entendu, vous pouvez utiliser des modèles C++ pour implémenter cette fonction pour tous les types pertinents avec un seul morceau de code.

La bibliothèque standard C fournit fmod, si je me souviens bien du nom, pour les types à virgule flottante.

Pour les entiers, vous pouvez définir un modèle de fonction C++ qui renvoie toujours le reste non négatif (correspondant à la division euclidienne) sous la forme ...

#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);
}

... et écrivez simplement mod(a, b) au lieu de a%b.

Ici, le type Integer doit être un type entier signé.

Si vous souhaitez un comportement mathématique courant dans lequel le signe du reste est le même que celui du diviseur, vous pouvez par exemple le faire.

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 ); }

… Avec la même contrainte sur Integer, qu'il s'agisse d'un type signé.


¹ Parce que la division entière de Python est arrondie à l'infini négatif.

3

Oh, je déteste% design pour cela aussi ....

Vous pouvez convertir un dividende en non signé de la manière suivante:

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

result = (offset + dividend) % divider

où offset est le plus proche de (-INT_MIN) multiple du module, son ajout et sa soustraction ne changeront donc pas modulo. Notez qu'il a un type non signé et que le résultat sera un entier. Malheureusement, il ne peut pas convertir correctement les valeurs INT_MIN ... (- offset-1) car elles provoquent un débordement asymétrique. Mais cette méthode ne présente qu'un seul arithmétique supplémentaire par opération (et aucun conditionnel) lors de l'utilisation d'un diviseur constant. Elle est donc utilisable dans les applications de type DSP.

Il y a un cas spécial, où diviseur est 2N (puissance entière de deux), pour lequel modulo peut être calculé en utilisant une simple arithmétique et une logique au niveau du bit comme

dividend&(divider-1)

par exemple

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

Le moyen le plus courant et le moins compliqué consiste à utiliser Modulo avec cette fonction (fonctionne uniquement avec un diviseur positif):

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

Ce résultat juste correct s'il est négatif.

Aussi, vous pouvez tromper:

(p% q + q)% q

Il est très court mais utilisez deux% qui sont généralement lents.

2
Vovanium

Je crois qu'une autre solution à ce problème serait d'utiliser des variables de type long au lieu de int.

Je travaillais sur un code dans lequel l'opérateur% renvoyait une valeur négative, ce qui posait problème (pour générer des variables aléatoires uniformes sur [0,1], vous ne voulez pas vraiment de nombres négatifs :)), mais après avoir basculé les variables sur tapez long, tout se passait bien et les résultats correspondaient à ceux que je obtenais en exécutant le même code dans python (important pour moi car je voulais pouvoir générer les mêmes nombres "aléatoires" sur plusieurs plates-formes.

2
david

Voici une nouvelle réponse à une vieille question, basée sur ceci document de recherche Microsoft et les références qui y figurent.

Notez que depuis C11 et C++ 11, la sémantique de div est devenue une troncature vers zéro (voir [expr.mul]/4). De plus, pour D divisé par d, C++ 11 garantit ce qui suit concernant le quotient qT et le reste rT

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));

signum correspond à -1, 0, +1, selon que son argument est <, ==,> ou plus que 0 (voir cette Q & A pour le code source).

Avec une division tronquée, , le signe du reste est égal au signe du dividende D, c'est-à-dire -1 % 8 == -1. C++ 11 fournit également un std::div fonction qui retourne une structure avec les membres quot et rem en fonction de la division tronquée.

Il existe d’autres définitions possibles, par exemple: la division dite au sol peut être définie en termes de division tronquée intégrée

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));

Avec division au sol, , le signe du reste est égal au signe du diviseur d. Dans des langues telles que Haskell et Oberon, il existe des opérateurs intégrés pour la division Floored. En C++, vous devez écrire une fonction en utilisant les définitions ci-dessus.

Une autre méthode est la division euclidienne , qui peut également être définie en termes de division tronquée intégrée.

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);

Avec la division euclidienne, le signe du reste est toujours positif .

1
TemplateRex
/* Attention: le mod macro évalue les effets secondaires de ses arguments plusieurs fois. */
 # définir mod (r, m) (((r)% (m)) + ((r) <0)? (m): 0) 

... ou simplement vous habituer à trouver un représentant pour la classe d'équivalence.

1
Eric Towers

Exemple de template pour C++

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

Avec ce modèle, le reste renvoyé sera égal à zéro ou aura le même signe que le diviseur (dénominateur) (l’équivalent de l’arrondi vers l’infini négatif), au lieu du comportement C++ du reste restant égal à zéro ou ayant le même signe que le dividende ( numérateur) (l’équivalent de l’arrondi vers zéro).

0
rcgldr