JavaScript% (modulo) fornisce un risultato negativo per i numeri negativi


253

Secondo Google Calculator (-13) % 64 è 51.

Secondo Javascript (vedi questo JSBin ) lo è -13.

Come posso risolvere questo problema?


Questo potrebbe essere solo un problema di precedenza. Intendi (-13) % 64o -(13 % 64)? Personalmente, metterei le parentesi in entrambi i modi, solo per maggiore chiarezza.
MatrixFrog,

2
essenzialmente un duplicato di In che modo Java esegue i calcoli dei moduli con numeri negativi? anche se questa è una domanda javascript.
Presidente James K. Polk,

85
Javascript a volte sembra uno scherzo molto crudele
dukeofgaming il

6
google non può essere sbagliato
caub

10
Il problema fondamentale è che in JS %non è l'operatore modulo. È l'operatore residuo. Non esiste un operatore modulo in JavaScript. Quindi la risposta accettata è la strada da percorrere.
Redu,

Risposte:


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

Tratto da questo articolo: JavaScript Modulo Bug


23
Non so che lo definirei un "bug". L'operazione modulo non è molto ben definita su numeri negativi e diversi ambienti di elaborazione la gestiscono in modo diverso. L'articolo di Wikipedia sull'operazione del modulo lo copre abbastanza bene.
Daniel Pryden,

22
Può sembrare stupido poiché viene spesso chiamato 'modulo', suggerendo che si comporterebbe come la sua definizione matematica (vedi algebra ℤ / nℤ), cosa che non accade.
etienne,

7
Perché prendere il modulo prima di aggiungere n? Perché non basta aggiungere n e quindi prendere il modulo?
inamidato il

12
@starwed se non hai usato questo% n fallirebbe, ad x < -nesempio (-7 + 5) % 5 === -2ma ((-7 % 5) + 5) % 5 == 3.
fadedbee,

7
Consiglio di aggiungere alla risposta che per accedere a questa funzione si dovrebbe usare il formato (-13) .mod (10) invece di -13% 10. Sarebbe più chiaro.
Jp_

161

Using Number.prototypeis SLOW, perché ogni volta che usi il metodo prototipo il tuo numero viene racchiuso in un Object. Invece di questo:

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

Uso:

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

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

~ 97% più veloce rispetto all'uso del prototipo. Se le prestazioni sono importanti per te, ovviamente ...


1
Ottimo consiglio Ho preso il tuo jsperf e confrontato con il resto delle soluzioni in questa domanda (ma sembra che questo sia il migliore comunque): jsperf.com/negative-modulo/3
Mariano Desanze,

11
Micro-ottimizzazione. Dovresti fare una quantità enorme di calcoli mod per fare questo per fare la differenza. Codifica ciò che è più chiaro e più gestibile, quindi ottimizza dopo l'analisi delle prestazioni.
ChrisV,

Credo che hai la tua ns e ms in tutto il modo sbagliato nel secondo esempio @StuR. Dovrebbe essere return ((n % m) + m) % m;.
vimist

Questo dovrebbe essere un commento alla risposta accettata, non una risposta per sé.
xehpuk,

5
La motivazione dichiarata in questa risposta è una micro-ottimizzazione, sì, ma la modifica del prototipo è problematica. Preferisci l'approccio con il minor numero di effetti collaterali, che è questo.
Appassionato

31

L' %operatore in JavaScript è l'operatore residuo, non l'operatore modulo (la differenza principale sta nel modo in cui vengono trattati i numeri negativi):

-1 % 8 // -1, not 7


8
Si dovrebbe essere chiamato l'operatore resto, ma è chiamato operatore modulo: developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/...
Big McLargeHuge

16
@DaveKennedy: MDN non è un riferimento linguistico ufficiale, è un sito modificato dalla comunità che a volte sbaglia. La specifica non lo chiama un operatore modulo e, per quanto ne so, non l'ha mai fatto (sono tornato a ES3). Dice esplicitamente che l'operatore produce il resto di una divisione implicita e lo chiama semplicemente "l'operatore%".
TJ Crowder,

2
Se viene chiamato remainder, deve essere maggiore di 0 per definizione. Non ricordi il teorema di divisione del liceo ?! Quindi forse puoi dare un'occhiata qui: en.wikipedia.org/wiki/Euclidean_division
Ahmad

19

Una funzione "mod" per restituire un risultato positivo.

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

Ed ovviamente

mod(-13,64) // 51

1
MDN non è un riferimento linguistico ufficiale, è un sito modificato dalla comunità che a volte sbaglia. La specifica non lo chiama un operatore modulo e, per quanto ne so, non l'ha mai fatto (sono tornato a ES3). Dice esplicitamente che l'operatore produce il resto di una divisione implicita e lo chiama semplicemente "l'operatore%".
TJ Crowder,

1
Oops, il link che hai specificato in realtà fa riferimento #sec-applying-the-mod-operatorproprio lì nell'URL :) Comunque, grazie per la nota, ho tolto la lanugine dalla mia risposta, non è comunque molto importante.
Shanimal,

3
@ Shanimal: LOL! Lo fa. Un errore dell'editor HTML. Il testo della specifica no.
TJ Crowder,

10

La risposta accettata mi rende un po 'nervoso perché riutilizza l'operatore%. Cosa succede se Javascript cambia il comportamento in futuro?

Ecco una soluzione alternativa che non riutilizza%:

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
Se javascript modificasse l'operatore modulo in modo che corrispondesse alla definizione matematica, la risposta accettata continuerebbe a funzionare.
inamidato il

20
"Cosa succede se Javascript cambia il comportamento in futuro?" - Perché dovrebbe? Cambiare il comportamento di un operatore così fondamentale non è probabile.
nnnnnn,

1
+1 per la condivisione di questa preoccupazione e alternativa alla risposta in evidenza # answer-4467559 e per 4 motivi: (1) Perché afferma, e sì "Cambiare il comportamento di tale operazione fondamentale non è probabile" ma è comunque prudente considerare anche per scoprire che non è necessario. (2) definire un'opera funzionante in termini di una rotta, sebbene impressionante, è preoccupante almeno al primo sguardo, non dovrebbe essere mostrato fino a che (3) anche se non avessi verificato bene questa alternativa, trovo più facile seguire sguardo veloce. (4) tiny: usa 1 div + 1 mul invece di 2 (mod) div e ho sentito su MOLTO hardware precedente con una buona FPU, la moltiplicazione era più veloce.
Destiny Architect,

2
@DestinyArchitect non è prudente, è inutile. Se dovessero cambiare il comportamento dell'operatore rimanente, si romperà una buona gamma di programmi che lo utilizzano. Non succederà mai.
Aegis,

10
Che cosa succede se il comportamento di -, *, /, ;, ., (, ), ,, Math.floor, functiono returncambiamenti? Quindi il tuo codice è orribilmente rotto.
xehpuk,

5

Sebbene non si stia comportando come previsto, ciò non significa che JavaScript non si stia "comportando". È una scelta JavaScript fatta per il suo calcolo del modulo. Perché, per definizione, entrambe le risposte hanno un senso.

Vedi questo da Wikipedia. A destra puoi vedere come diverse lingue hanno scelto il segno del risultato.


4

Se xè un numero intero ed nè una potenza di 2, è possibile utilizzare x & (n - 1)invece di x % n.

> -13 & (64 - 1)
51 

2

Quindi sembra che se stai cercando di modificare i gradi (in modo che se hai -50 gradi - 200 gradi), vorresti usare qualcosa come:

function modrad(m) {
    return ((((180+m) % 360) + 360) % 360)-180;
}

1

Ho a che fare con négative ae negative 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

Questo non è un bug, ci sono 3 funzioni per calcolare il modulo, puoi usare quello che si adatta alle tue esigenze (consiglierei di usare la funzione euclidea)

Troncare la funzione della parte decimale

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

Funzione parte intera

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

Funzione euclidea

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
Nella funzione euclidea il controllo m <0 è inutile perché ((questo% n) + n)% n è sempre positivo
bormat

1
@bormat Sì, ma in Javascript %può restituire risultati negativi (questo è lo scopo di queste funzioni, per risolverlo)
zessx

hai scritto questo [codice] Number.prototype.mod = function (n) {var m = ((this% n) + n)% n; ritorna m <0? m + Math.abs (n): m; }; [/ code] mi dà un valore di n dove m è négativo. non hanno valore di n dove m è négativo perché aggiungi n dopo il primo%.
Bormat,

Senza questo controllo, parseInt(-41).mod(-7)verrebbe restituito -6anziché 1(e questo è esattamente lo scopo della funzione di parte Intero che ho scritto)
zessx

1
Puoi semplificare la tua funzione rimuovendo il secondo modulo Number.prototype.mod = function (n) {var m = this% n; ritorno (m <0)? m + Math.abs (n): m; };
Bormat,

0

C'è un pacchetto NPM che farà il lavoro per te. Puoi installarlo con il seguente comando.

npm install just-modulo --save

Utilizzo copiato dal file 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

Il repository GitHub può essere trovato tramite il seguente link:

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

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.