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.