La meilleure façon de faire en sorte que le module de Java se comporte comme il se doit avec des nombres négatifs?

103

En java quand vous le faites

a % b

Si a est négatif, il renverra un résultat négatif, au lieu de revenir à b comme il se doit. Quelle est la meilleure façon de résoudre ce problème? La seule façon dont je peux penser est

a < 0 ? b + a : a % b
fent
la source
12
Il n'y a pas de comportement de module «correct» lorsqu'il s'agit de nombres négatifs - beaucoup de langages le font de cette façon, beaucoup de langages le font différemment, et quelques langages font quelque chose de complètement différent. Au moins les deux premiers ont leurs avantages et leurs inconvénients.
4
c'est juste bizarre pour moi. Je pensais qu'il ne devrait retourner négatif que si b est négatif.
fent
2
c'est. mais le titre de cette question devrait être renommé. Je ne cliquerais pas sur cette question si je cherchais celle-ci car je sais déjà comment fonctionne le module Java.
fent
4
Je l'ai juste renommé en "Pourquoi -13% 64 = 51?", Ce qui ne serait jamais quelque chose que quelqu'un chercherait dans un million d'années. Donc, ce titre de question est bien meilleur et beaucoup plus consultable sur des mots-clés tels que module, négatif, calcul, nombres.
Erick Robertson

Réponses:

144

Il se comporte comme il se doit a% b = a - a / b * b; c'est à dire que c'est le reste.

Vous pouvez faire (a% b + b)% b


Cette expression fonctionne comme le résultat de (a % b)est nécessairement inférieur à b, peu importe si aest positif ou négatif. L'ajout bprend en charge les valeurs négatives de a, puisque (a % b)est une valeur négative entre -bet 0, (a % b + b)est nécessairement inférieure à bet positive. Le dernier modulo est là au cas où aétait positif au départ, car si aest positif (a % b + b)deviendrait plus grand que b. Par conséquent, le (a % b + b) % btransforme en plus petit que de bnouveau (et n'affecte pas les avaleurs négatives ).

Peter Lawrey
la source
3
cela fonctionne mieux grâce. et cela fonctionne aussi pour les nombres négatifs qui sont beaucoup plus grands que b.
fent
6
Cela fonctionne puisque le résultat de (a % b)est nécessairement inférieur à b(peu importe s'il aest positif ou négatif), l'addition bprend en charge les valeurs négatives de a, car (a % b)est inférieur à bet inférieur à 0, (a % b + b)est nécessairement inférieur à bet positif. Le dernier modulo est là au cas où aétait positif au départ, car si aest positif (a % b + b)deviendrait plus grand que b. Par conséquent, le (a % b + b) % btransforme en plus petit que bnouveau (et n'affecte pas les avaleurs négatives ).
ethanfar
1
@eitanfar J'ai inclus votre excellente explication dans la réponse (avec une correction mineure pour a < 0, peut-être que vous pourriez y jeter un coup d'œil)
Maarten Bodewes
5
Je viens de voir cela commenté sur une autre question concernant le même sujet; Il pourrait être intéressant de mentionner que cela (a % b + b) % bse décompose pour les très grandes valeurs de aet b. Par exemple, utiliser a = Integer.MAX_VALUE - 1et b = Integer.MAX_VALUEdonnera -3comme résultat, qui est un nombre négatif, ce que vous vouliez éviter.
Thorbear
2
@Mikepote en utilisant un whileserait plus lent si vous en avez vraiment besoin, sauf que vous n'avez besoin que d'un, ifauquel cas il est en fait plus rapide.
Peter Lawrey
92

A partir de Java 8, vous pouvez utiliser Math.floorMod (int x, int y) et Math.floorMod (long x, long y) . Ces deux méthodes renvoient les mêmes résultats que la réponse de Peter.

Math.floorMod( 2,  3) =  2
Math.floorMod(-2,  3) =  1
Math.floorMod( 2, -3) = -1
Math.floorMod(-2, -3) = -2
John Krueger
la source
1
meilleure réponse pour Java 8+
Charney Kaye
Cool, je ne savais pas pour celui-là. Java 8 a définitivement corrigé quelques PITA.
Franz D.
4
Bonne façon. Mais malheureusement , ne fonctionne pas avec floatou doublearguments. L'opérateur binaire Mod ( %) fonctionne également avec les opérandes floatet double.
Mir-Ismaili
11

Pour ceux qui n'utilisent pas (ou ne peuvent pas encore utiliser) Java 8, Guava est venu à la rescousse avec IntMath.mod () , disponible depuis Guava 11.0.

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

Une mise en garde: contrairement à Math.floorMod () de Java 8, le diviseur (le deuxième paramètre) ne peut pas être négatif.

Ibrahim Arief
la source
7

En théorie des nombres, le résultat est toujours positif. Je suppose que ce n'est pas toujours le cas dans les langages informatiques car tous les programmeurs ne sont pas des mathématiciens. Mes deux cents, je considérerais cela comme un défaut de conception du langage, mais vous ne pouvez pas le changer maintenant.

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

car 180 * (-1) + 176 = -4 identique à 180 * 0 + 176 = 176

En utilisant l'exemple d'horloge ici, http://mathworld.wolfram.com/Congruence.html vous ne diriez pas que duration_of_time mod cycle_length est de -45 minutes, vous diriez 15 minutes, même si les deux réponses satisfont l'équation de base.

Chris Golledge
la source
1
En théorie des nombres, ce n'est pas toujours positif ... Ils tombent dans des classes de congruence. Vous êtes libre de choisir n'importe quel candidat de cette classe pour vos besoins de notation, mais l'idée est qu'il mappe à toute cette classe, et si l'utilisation d'un autre candidat spécifique de cette classe rend un certain problème beaucoup plus simple (choisir -1au lieu de, n-1par exemple) alors ayez à lui.
BeUndead
2

Java 8 a Math.floorMod, mais il est très lent (son implémentation a plusieurs divisions, multiplications et un conditionnel). Cependant, il est possible que la JVM dispose d'un stub intrinsèque optimisé, ce qui l'accélérerait considérablement.

Le moyen le plus rapide de faire cela sans floorModest comme d'autres réponses ici, mais sans branches conditionnelles et une seule opération lente %.

En supposant que n est positif et que x peut être n'importe quoi:

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;

Les résultats lorsque n = 3:

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

Si vous avez seulement besoin d 'une distribution uniforme entre 0et n-1et non l' opérateur de mod exact, et que vos xne se regroupent pas près 0, ce qui suit sera encore plus rapide, car il y a plus de parallélisme au niveau des instructions et le %calcul lent se produira en parallèle avec l 'autre pièces car elles ne dépendent pas de son résultat.

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

Les résultats pour ce qui précède avec 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

Si l'entrée est aléatoire dans la plage complète d'un entier, la distribution des deux solutions sera la même. Si les grappes d'entrée sont proches de zéro, il y aura trop peu de résultats n - 1dans cette dernière solution.

Scott Carey
la source
1

Voici une alternative:

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

Cela pourrait ou non être plus rapide que cette autre formule [(a% b + b)% b]. Contrairement à l'autre formule, elle contient une branche, mais utilise une opération modulo en moins. Probablement une victoire si l'ordinateur peut prédire correctement un <0.

(Edit: Correction de la formule.)

Stefan Reich
la source
1
Mais l'opération modulo nécessite une division qui pourrait être encore plus lente (surtout si le processeur devine correctement la branche presque tout le temps). Donc c'est peut-être mieux.
dave
@KarstenR. Vous avez raison! J'ai corrigé la formule, maintenant cela fonctionne bien (mais a besoin de deux soustractions supplémentaires).
Stefan Reich
C'est vrai @dave
Stefan Reich