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?


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


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 ..


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


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

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

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.


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 

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

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

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

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.