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?


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


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


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


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

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

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.


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 

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

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

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

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.