JavaScript% (módulo) fornece um resultado negativo para números negativos

253

De acordo com o Google Calculator (-13) % 64 é 51.

De acordo com o Javascript (veja este JSBin ) é -13.

Como faço para corrigir isso?

Alec Gorge
fonte
Isso pode ser apenas um problema de precedência. Você quer dizer (-13) % 64ou -(13 % 64)? Pessoalmente, eu colocaria os parênteses de qualquer maneira, apenas para maior clareza.
MatrixFrog
2
essencialmente uma duplicata de Como o java faz cálculos de módulo com números negativos? mesmo que seja uma pergunta sobre javascript.
Presidente James K. Polk,
85
Javascript às vezes se sente como uma piada muito cruel
dukeofgaming
6
google não pode estar errado
caub
10
O problema fundamental está no JS %não é o operador do módulo. É o operador restante. Não há operador de módulo em JavaScript. Portanto, a resposta aceita é o caminho a percorrer.
Redu

Respostas:

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

Retirado deste artigo: The JavaScript Modulo Bug

Enrique
fonte
23
Não sei se chamaria de "bug". A operação do módulo não está muito bem definida sobre números negativos, e diferentes ambientes de computação lidam com isso de maneira diferente. O artigo da Wikipedia sobre a operação do módulo cobre muito bem.
Daniel Pryden
22
Pode parecer idiota, já que é freqüentemente chamado de 'módulo', sugerindo que se comportaria da mesma forma que sua definição matemática (consulte ℤ / nℤ álgebra), o que não é.
Etienne
7
Por que fazer o módulo antes de adicionar n? Por que não apenas adicionar n e depois fazer o módulo?
starwed 26/11/13
12
@starwed se você não usasse este% n, falharia x < -n- por exemplo, (-7 + 5) % 5 === -2mas ((-7 % 5) + 5) % 5 == 3.
fadedbee
7
Eu recomendo acrescentar à resposta que, para acessar esta função, deve-se usar o formato (-13) .mod (10) em vez de -13% 10. Seria mais claro.
Jp_
161

O uso Number.prototypeé LENTO, porque cada vez que você usa o método prototype, seu número é envolvido em um Object. Em vez disso:

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

Usar:

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

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

~ 97% mais rápido do que usar protótipo. Se o desempenho é importante para você, é claro ..

StuR
fonte
1
Ótima dica. Tomei seu JSPerf e comparado com o resto das soluções nesta questão (mas parece que este é o melhor de qualquer maneira): jsperf.com/negative-modulo/3
Mariano Desanze
11
Micro-otimização. Você teria que fazer uma quantidade enorme de cálculos de mods para que isso fizesse alguma diferença. Codifique o que é mais claro e mais sustentável e, em seguida, otimize após a análise de desempenho.
ChrisV
Eu acho que você tem seus ns e ms em torno da maneira errada em seu segundo exemplo @StuR. Deveria ser return ((n % m) + m) % m;.
vimist
Isso deve ser um comentário para a resposta aceita, não uma resposta por si só.
xehpuk
5
A motivação declarada nesta resposta é uma micro-otimização, sim, mas modificar o protótipo é problemático. Prefira a abordagem com o menor número de efeitos colaterais, que é esse.
Aproximando
31

O %operador em JavaScript é o operador restante, não o operador de módulo (a principal diferença está na maneira como os números negativos são tratados):

-1 % 8 // -1, not 7

Rob Sobers
fonte
8
Ele deve ser chamado o operador de resto mas é chamado de operador de módulo: developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/...
Big McLargeHuge
16
@DaveKennedy: MDN não é uma referência de idioma oficial, é um site editado pela comunidade que às vezes erra. As especificações não o chamam de operador de módulo, e até onde eu sei, nunca o fiz (voltei ao ES3). Diz explicitamente que o operador produz o restante de uma divisão implícita e apenas o chama de "operador%".
TJ Crowder
2
Se for chamado remainder, deve ser maior que 0 por definição. Você não consegue se lembrar do teorema da divisão do ensino médio ?! Então, talvez você possa dar uma olhada aqui: en.wikipedia.org/wiki/Euclidean_division
Ahmad
19

Uma função "mod" para retornar um resultado 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

E claro

mod(-13,64) // 51
Shanimal
fonte
1
MDN não é uma referência de idioma oficial, é um site editado pela comunidade que às vezes erra. As especificações não o chamam de operador de módulo, e até onde eu sei, nunca o fiz (voltei ao ES3). Diz explicitamente que o operador produz o restante de uma divisão implícita e apenas o chama de "operador%".
TJ Crowder
1
Ops, o link que você especificou realmente faz referência #sec-applying-the-mod-operatorali no URL :) De qualquer forma, obrigado pela observação, tirei o cotão da minha resposta, não é realmente importante de qualquer maneira.
Shanimal
3
@ Shanimal: LOL! Faz. Um erro do editor de HTML. O texto de especificação não.
TJ Crowder
10

A resposta aceita me deixa um pouco nervoso porque reutiliza o operador%. E se o Javascript mudar o comportamento no futuro?

Aqui está uma solução alternativa que não reutiliza%:

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
fonte
8
Se o javascript alterasse o operador do módulo para corresponder à definição matemática, a resposta aceita ainda funcionaria.
starwed
20
"E se o Javascript mudar o comportamento no futuro?" - Por que isso? Alterar o comportamento de um operador tão fundamental não é provável.
Nnnnnn
1
+1 por compartilhar esta preocupação - e alternativa - à resposta em destaque # answer-4467559 e por 4 razões: (1) Por que afirma, e sim "Não é provável que seja possível mudar o comportamento de uma operação tão fundamental", mas seja prudente considerar mesmo para descobrir que não é necessário. (2) definir uma operação que funcione em termos de uma operação quebrada, embora impressionante, é preocupante, pelo menos à primeira vista, e deve ser mostrado até que não seja (3) que eu não verifiquei bem essa alternativa, acho mais fácil segui-la uma olhadela. (4) tiny: usa 1 div + 1 mul em vez de 2 (mod) divs & eu ouvi em MUITO hardware anterior sem um bom FPU, a multiplicação foi mais rápida.
Destino Architect
2
@DestinyArchitect não é prudente, é inútil. Se eles mudassem o comportamento do operador restante, isso quebraria uma boa variedade de programas usando-o. Isso nunca vai acontecer.
Garra
10
E se o comportamento de -, *, /, ;, ., (, ), ,, Math.floor, functionou returnmudanças? Então seu código está terrivelmente quebrado.
xehpuk
5

Embora não esteja se comportando como o esperado, isso não significa que o JavaScript não esteja 'se comportando'. É uma escolha feita pelo JavaScript para o cálculo do módulo. Porque, por definição, qualquer resposta faz sentido.

Veja isso na Wikipedia. Você pode ver à direita como diferentes idiomas escolheram o sinal do resultado.

dheerosaur
fonte
4

Se xé um número inteiro e né uma potência de 2, você pode usar em x & (n - 1)vez de x % n.

> -13 & (64 - 1)
51 
quasimodo
fonte
2

Portanto, parece que se você estiver tentando modificar graus (de modo que se você tiver -50 graus - 200 graus), deseje usar algo como:

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

Eu lido com o negativo A e negativo também

 //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
fonte
1

Isso não é um bug, existem 3 funções para calcular o módulo, você pode usar o que se adapta às suas necessidades (eu recomendaria o uso da função euclidiana)

Truncar a função da parte decimal

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

Função de peça inteira

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

Função euclidiana

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
fonte
1
Em euclidiana função de verificação m <0 é inútil porque ((este% n) + N)% n é sempre positivo
bormat
1
@bormat Sim, é, mas em Javascript %pode retornar resultados negativos (um este é o propósito dessas funções, para corrigi-lo)
zessx
você escreveu este [código] Number.prototype.mod = function (n) {var m = ((this% n) + n)% n; retornar m <0? m + Math.abs (n): m; }; [/ código] me dê um valor de n onde m é negativo. eles não têm valor de n onde m é negativo porque você adiciona n após o primeiro%.
Bormat #
Sem essa verificação, parseInt(-41).mod(-7)voltaria -6em vez de 1(e este é exatamente o propósito da função de parte Integer eu escrevi)
zessx
1
Você pode simplificar sua função removendo o segundo módulo Number.prototype.mod = function (n) {var m = this% n; retorno (m <0)? m + Math.abs (n): m; };
bormat
0

Há um pacote NPM que fará o trabalho para você. Você pode instalá-lo com o seguinte comando.

npm install just-modulo --save

Uso copiado do 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

O repositório do GitHub pode ser encontrado através do seguinte link:

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

maartenpaauw
fonte