JavaScript% (modulo) donne un résultat négatif pour les nombres négatifs

253

Selon Google Calculator (-13) % 64 est 51.

Selon Javascript (voir ce JSBin ) c'est le cas -13.

Comment puis-je réparer ça?

Gorges d'Alec
la source
Cela peut simplement être une question de priorité. Voulez-vous dire (-13) % 64ou -(13 % 64)? Personnellement, je mettrais les parens de toute façon, juste pour plus de clarté.
MatrixFrog
2
essentiellement un doublon de Comment java fait-il les calculs de module avec des nombres négatifs? même s'il s'agit d'une question javascript.
président James K. Polk,
85
Javascript ressemble parfois à une blague très cruelle
dukeofgaming
6
google ne peut pas se tromper
caub
10
Le problème fondamental est que JS %n'est pas l'opérateur modulo. C'est l'opérateur restant. Il n'y a pas d'opérateur modulo en JavaScript. La réponse acceptée est donc la voie à suivre.
Redu

Réponses:

263
Number.prototype.mod = function(n) {
    return ((this%n)+n)%n;
};

Tiré de cet article: Le bogue JavaScript Modulo

Enrique
la source
23
Je ne sais pas si je l'appellerais un "bug". L'opération modulo n'est pas très bien définie sur des nombres négatifs et différents environnements informatiques la gèrent différemment. L'article de Wikipédia sur l' opération modulo le couvre assez bien.
Daniel Pryden
22
Il peut sembler stupide car il est souvent appelé «modulo», ce qui suggère qu'il se comporterait de la même manière que sa définition mathématique (voir l'algèbre ℤ / nℤ), ce qu'il ne fait pas.
etienne
7
Pourquoi prendre le modulo avant d'ajouter n? Pourquoi ne pas simplement ajouter n puis prendre le modulo?
Starwed
12
@starwed si vous n'avez pas utilisé ce% n, il échouerait x < -n- par exemple, (-7 + 5) % 5 === -2mais ((-7 % 5) + 5) % 5 == 3.
fadedbee
7
Je recommande d'ajouter à la réponse que pour accéder à cette fonction, il faut utiliser le format (-13) .mod (10) au lieu de -13% 10. Ce serait plus clair.
Jp_
161

L'utilisation Number.prototypeest LENTE, car chaque fois que vous utilisez la méthode prototype, votre numéro est enveloppé dans un Object. Au lieu de cela:

Number.prototype.mod = function(n) {
  return ((this % n) + n) % n;
}

Utilisation:

function mod(n, m) {
  return ((n % m) + m) % m;
}

Voir: http://jsperf.com/negative-modulo/2

~ 97% plus rapide que l'utilisation d'un prototype. Si les performances sont importantes pour vous, bien sûr ..

StuR
la source
1
Bon conseil. J'ai pris votre jsperf et comparé avec le reste des solutions dans cette question (mais il semble que ce soit le meilleur de toute façon): jsperf.com/negative-modulo/3
Mariano Desanze
11
Micro-optimisation. Vous devez faire une énorme quantité de calculs de mod pour que cela fasse la différence. Codez ce qui est le plus clair et le plus facile à entretenir, puis optimisez après l'analyse des performances.
ChrisV
Je pense que vous avez vos ns et ms autour de la mauvaise façon dans votre deuxième exemple @StuR. Ça devrait l'être return ((n % m) + m) % m;.
vimist
Cela devrait être un commentaire à la réponse acceptée, pas une réponse pour elle-même.
xehpuk
5
La motivation indiquée dans cette réponse est une micro-optimisation, oui, mais la modification du prototype est problématique. Préférez l'approche avec le moins d'effets secondaires, qui est celle-ci.
Vif
31

L' %opérateur en JavaScript est l'opérateur restant, pas l'opérateur modulo (la principale différence étant la façon dont les nombres négatifs sont traités):

-1 % 8 // -1, not 7

Rob Sobers
la source
8
Il doit être appelé l'opérateur restant, mais il est appelé opérateur de module: developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/…
Big McLargeHuge
16
@DaveKennedy: MDN n'est pas une référence de langue officielle, c'est un site édité par la communauté qui se trompe parfois. La spécification ne l'appelle pas un opérateur modulo, et pour autant que je sache, elle ne l'a jamais fait (je suis retourné à ES3). Il dit explicitement que l'opérateur cède le reste d'une division implicite et l'appelle simplement "l'opérateur%".
TJ Crowder du
2
S'il est appelé remainder, il doit être supérieur à 0 par définition. Vous ne vous souvenez pas du théorème de division du lycée?! Alors peut-être que vous pouvez jeter un œil ici: en.wikipedia.org/wiki/Euclidean_division
Ahmad
19

Une fonction "mod" pour retourner un résultat positif.

var mod = function (n, m) {
    var remain = n % m;
    return Math.floor(remain >= 0 ? remain : remain + m);
};
mod(5,22)   // 5
mod(25,22)  // 3
mod(-1,22)  // 21
mod(-2,22)  // 20
mod(0,22)   // 0
mod(-1,22)  // 21
mod(-21,22) // 1

Et bien sûr

mod(-13,64) // 51
Shanimal
la source
1
MDN n'est pas une référence de langue officielle, c'est un site édité par la communauté qui se trompe parfois. La spécification ne l'appelle pas un opérateur modulo, et pour autant que je sache, elle ne l'a jamais fait (je suis retourné à ES3). Il dit explicitement que l'opérateur cède le reste d'une division implicite, et l'appelle simplement "l'opérateur%".
TJ Crowder du
1
Oups, le lien que vous avez spécifié fait référence #sec-applying-the-mod-operatorjuste ici dans l'url :) Quoi qu'il en soit, merci pour la note, j'ai retiré le fluff de ma réponse, ce n'est pas vraiment important de toute façon.
Shanimal
3
@ Shanimal: LOL! Cela fait. Une erreur de l'éditeur HTML. Le texte de spécification ne fonctionne pas.
TJ Crowder
10

La réponse acceptée me rend un peu nerveux car il réutilise l'opérateur%. Et si Javascript change le comportement à l'avenir?

Voici une solution de contournement qui ne réutilise pas%:

function mod(a, n) {
    return a - (n * Math.floor(a/n));
}

mod(1,64); // 1
mod(63,64); // 63
mod(64,64); // 0
mod(65,64); // 1
mod(0,64); // 0
mod(-1,64); // 63
mod(-13,64); // 51
mod(-63,64); // 1
mod(-64,64); // 0
mod(-65,64); // 63
wisbucky
la source
8
Si javascript modifiait l'opérateur modulo pour correspondre à la définition mathématique, la réponse acceptée fonctionnerait toujours.
Starwed
20
"Et si Javascript change le comportement à l'avenir?" - Pourquoi ça? Il est peu probable que le comportement d'un tel opérateur fondamental soit modifié.
nnnnnn
1
+1 pour avoir partagé cette préoccupation - et alternative - à la réponse en vedette # answer-4467559 et pour 4 raisons: (1) Pourquoi il est indiqué, et oui «Il est peu probable que le comportement d'un tel op fondamental soit modifié», mais il est toujours prudent d'envisager même pour trouver que ce n'est pas nécessaire. (2) définir une opération de travail en termes d'une opération cassée, bien qu'impressionnant, est inquiétant au moins au premier coup d'œil, il devrait être montré jusqu'à ce que non (3) alors que j'ai bien vérifié cette alternative, je trouve plus facile à suivre coup d'oeil. (4) minuscule: il utilise 1 div + 1 mul au lieu de 2 (mod) divs et j'ai entendu sur BEAUCOUP de matériel antérieur sans un bon FPU, la multiplication était plus rapide.
Destiny Architect
2
@DestinyArchitect ce n'est pas prudent, c'est inutile. S'ils devaient changer le comportement de l'opérateur restant, cela casserait une bonne gamme de programmes l'utilisant. Cela n'arrivera jamais.
Aegis
10
Que faire si le comportement -, *, /, ;, ., (, ), ,, Math.floor, functionou des returnchangements? Ensuite, votre code est horriblement cassé.
xehpuk
5

Bien qu'il ne se comporte pas comme prévu, cela ne signifie pas que JavaScript ne se comporte pas. C'est un choix que JavaScript a fait pour son calcul modulo. Parce que, par définition, chaque réponse a du sens.

Voir cela sur Wikipedia. Vous pouvez voir à droite comment différentes langues ont choisi le signe du résultat.

dhérosaurus
la source
4

Si xest un entier et a nune puissance de 2, vous pouvez utiliser à la x & (n - 1)place de x % n.

> -13 & (64 - 1)
51 
quasimodo
la source
2

Il semble donc que si vous essayez de modifier les degrés (de sorte que si vous avez -50 degrés - 200 degrés), vous voudriez utiliser quelque chose comme:

function modrad(m) {
    return ((((180+m) % 360) + 360) % 360)-180;
}
JayCrossler
la source
1

Je gère aussi le négatif a et le négatif n

 //best perf, hard to read
   function modul3(a,n){
        r = a/n | 0 ;
        if(a < 0){ 
            r += n < 0 ? 1 : -1
        }
        return a - n * r 
    }
    // shorter code
    function modul(a,n){
        return  a%n + (a < 0 && Math.abs(n)); 
    }

    //beetween perf and small code
    function modul(a,n){
        return a - n * Math[n > 0 ? 'floor' : 'ceil'](a/n); 
    }
bormat
la source
1

Ce n'est pas un bug, il y a 3 fonctions pour calculer le modulo, vous pouvez utiliser celle qui correspond à vos besoins (je recommanderais d'utiliser la fonction euclidienne)

Tronquer la fonction de partie décimale

console.log(  41 %  7 ); //  6
console.log( -41 %  7 ); // -6
console.log( -41 % -7 ); // -6
console.log(  41 % -7 ); //  6

Fonction de partie entière

Number.prototype.mod = function(n) {
    return ((this%n)+n)%n;
};

console.log( parseInt( 41).mod( 7) ); //  6
console.log( parseInt(-41).mod( 7) ); //  1
console.log( parseInt(-41).mod(-7) ); // -6
console.log( parseInt( 41).mod(-7) ); // -1

Fonction euclidienne

Number.prototype.mod = function(n) {
    var m = ((this%n)+n)%n;
    return m < 0 ? m + Math.abs(n) : m;
};

console.log( parseInt( 41).mod( 7) ); // 6
console.log( parseInt(-41).mod( 7) ); // 1
console.log( parseInt(-41).mod(-7) ); // 1
console.log( parseInt( 41).mod(-7) ); // 6
zessx
la source
1
Dans la fonction euclidienne, la vérification de m <0 est inutile car ((this% n) + n)% n est toujours positif
bormat
1
@bormat Oui c'est vrai, mais en Javascript %peut retourner des résultats négatifs (c'est le but de ces fonctions, pour y remédier)
zessx
vous avez écrit ce [code] Number.prototype.mod = function (n) {var m = ((this% n) + n)% n; retourner m <0? m + Math.abs (n): m; }; [/ code] donne moi une valeur de n où m est négatif. ils ne valent pas n où m est négatif car vous ajoutez n après le premier%.
bormat
Sans cette vérification, parseInt(-41).mod(-7)reviendrait -6au lieu de 1(et c'est exactement le but de la fonction de partie entière que j'ai écrite)
zessx
1
Vous pouvez simplifier votre fonction en supprimant le second modulo Number.prototype.mod = function (n) {var m = this% n; retour (m <0)? m + Math.abs (n): m; };
bormat
0

Il existe un package NPM qui fera le travail pour vous. Vous pouvez l'installer avec la commande suivante.

npm install just-modulo --save

Utilisation copiée à partir du fichier README

import modulo from 'just-modulo';

modulo(7, 5); // 2
modulo(17, 23); // 17
modulo(16.2, 3.8); // 17
modulo(5.8, 3.4); //2.4
modulo(4, 0); // 4
modulo(-7, 5); // 3
modulo(-2, 15); // 13
modulo(-5.8, 3.4); // 1
modulo(12, -1); // NaN
modulo(-3, -8); // NaN
modulo(12, 'apple'); // NaN
modulo('bee', 9); // NaN
modulo(null, undefined); // NaN

Le référentiel GitHub peut être trouvé via le lien suivant:

https://github.com/angus-c/just/tree/master/packages/number-modulo

maartenpaauw
la source