JavaScript% (módulo) da un resultado negativo para números negativos

253

De acuerdo con Google Calculator (-13) % 64 es 51.

De acuerdo con Javascript (ver este JSBin ) lo es -13.

¿Cómo puedo solucionar esto?

Garganta de Alec
fuente
Esto puede ser solo un problema de precedencia. ¿Quieres decir (-13) % 64o -(13 % 64)? Personalmente, pondría a los padres de cualquier manera, solo para mayor claridad.
MatrixFrog
2
esencialmente un duplicado de ¿Cómo hace Java los cálculos de módulo con números negativos? a pesar de que esta es una pregunta de JavaScript.
Presidente James K. Polk
85
Javascript a veces se siente como una broma muy cruel
dukeofgaming
66
google no puede estar equivocado
caub
10
El problema fundamental es que JS %no es el operador de módulo. Es el operador restante. No hay operador de módulo en JavaScript. Entonces la respuesta aceptada es el camino a seguir.
Reducido

Respuestas:

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

Tomado de este artículo: El error del módulo de JavaScript

Enrique
fuente
23
No sé si lo llamaría "error". La operación de módulo no está muy bien definida sobre números negativos, y diferentes entornos informáticos lo manejan de manera diferente. El artículo de Wikipedia sobre la operación de módulo lo cubre bastante bien.
Daniel Pryden
22
Puede parecer tonto ya que a menudo se le llama 'módulo', lo que sugiere que se comportaría de la misma manera que su definición matemática (ver álgebra ℤ / n which), que no es así.
etienne
77
¿Por qué tomar el módulo antes de agregar n? ¿Por qué no agregar simplemente ny luego tomar el módulo?
starwed
12
@starwed si no usaste este% n fallaría, por x < -nejemplo, (-7 + 5) % 5 === -2pero ((-7 % 5) + 5) % 5 == 3.
fadedbee
77
Recomiendo agregar a la respuesta que para acceder a esta función se debe usar el formato (-13) .mod (10) en lugar de -13% 10. Sería más claro.
Jp_
161

El uso Number.prototypees LENTO, porque cada vez que usa el método prototipo, su número se envuelve en un Object. En lugar de esto:

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

Utilizar:

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

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

~ 97% más rápido que usar un prototipo. Si el rendimiento es importante para usted, por supuesto ...

StuR
fuente
1
Gran consejo Tomé su jsperf y lo comparé con el resto de las soluciones en esta pregunta (pero parece que es la mejor de todas formas): jsperf.com/negative-modulo/3
Mariano Desanze
11
Microoptimización. Tendría que estar haciendo una gran cantidad de cálculos de modificación para que esto haga alguna diferencia. Codifique lo que sea más claro y más fácil de mantener, luego optimice el siguiente análisis de rendimiento.
ChrisV
Creo que usted tiene sus ns y mS en todo el camino equivocado en su segundo ejemplo @StuR. Debería ser return ((n % m) + m) % m;.
vimist
Esto debería ser un comentario a la respuesta aceptada, no una respuesta en sí misma.
xehpuk
55
La motivación indicada en esta respuesta es una microoptimización, sí, pero modificar el prototipo es problemático. Prefiere el enfoque con la menor cantidad de efectos secundarios, que es este.
Keen
31

El %operador en JavaScript es el operador restante, no el operador de módulo (la principal diferencia radica en cómo se tratan los números negativos):

-1 % 8 // -1, not 7

Rob Sobers
fuente
8
Se debe llamar al operador resto pero se llama operador de módulo: developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/...
Gran McLargeHuge
16
@DaveKennedy: MDN no es una referencia de idioma oficial, es un sitio editado por la comunidad que a veces se equivoca. La especificación no lo llama operador de módulo y, por lo que puedo decir, nunca lo ha hecho (volví a ES3). Dice explícitamente que el operador produce el resto de una división implícita, y simplemente lo llama "el operador%".
TJ Crowder
2
Si se llama remainder, debe ser mayor que 0 por definición. ¿No puedes recordar el teorema de división de la escuela secundaria? Así que quizás puedas echar un vistazo aquí: en.wikipedia.org/wiki/Euclidean_division
Ahmad
19

Una función "mod" para devolver un 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

Y por supuesto

mod(-13,64) // 51
Shanimal
fuente
1
MDN no es una referencia de idioma oficial, es un sitio editado por la comunidad que a veces se equivoca. La especificación no lo llama operador de módulo y, por lo que puedo decir, nunca lo ha hecho (volví a ES3). Dice explícitamente que el operador produce el resto de una división implícita, y simplemente lo llama "el operador%".
TJ Crowder
1
Vaya, el enlace que especificó en realidad hace referencia #sec-applying-the-mod-operatorallí mismo en la URL :) De todos modos, gracias por la nota, eliminé mi respuesta, de todos modos no es realmente importante.
Shanimal
3
@ Shanimal: LOL! Lo hace. Un error del editor HTML. El texto de especificación no.
TJ Crowder
10

La respuesta aceptada me pone un poco nervioso porque reutiliza el operador%. ¿Qué pasa si Javascript cambia el comportamiento en el futuro?

Aquí hay una solución alternativa que no 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
fuente
8
Si JavaScript cambiara el operador del módulo para que coincidiera con la definición matemática, la respuesta aceptada seguiría funcionando.
starwed
20
"¿Qué pasa si Javascript cambia el comportamiento en el futuro?" - ¿Por qué lo haría? Cambiar el comportamiento de un operador tan fundamental no es probable.
nnnnnn
1
+1 por compartir esta preocupación -y alternativa- a la respuesta presentada # answer-4467559 y por 4 razones: (1) Por qué dice, y sí "No es probable cambiar el comportamiento de una operación tan fundamental", pero aún es prudente considerar incluso para encontrar que no es necesario. (2) definir una operación de trabajo en términos de una rota, aunque impresionante, es preocupante al menos en el primer vistazo, debe mostrarse hasta que no (3) aunque no haya verificado bien esta alternativa, creo que es más fácil seguirla vistazo rápido. (4) pequeño: usa 1 div + 1 mul en lugar de 2 (mod) divs y he escuchado en MUCHO hardware anterior sin una buena FPU, la multiplicación fue más rápida.
Destiny Architect
2
@DestinyArchitect no es prudente, no tiene sentido. Si cambiaran el comportamiento del operador restante, se rompería una buena gama de programas que lo utilizan. Eso nunca va a suceder.
Aegis
10
¿Qué pasa si el comportamiento de -, *, /, ;, ., (, ), ,, Math.floor, functiono returncambios? Entonces tu código está horriblemente roto.
xehpuk
5

Aunque no se está comportando como esperaba, no significa que JavaScript no se esté "comportando". Es una elección de JavaScript para su cálculo de módulo. Porque, por definición, cualquier respuesta tiene sentido.

Vea esto de Wikipedia. Puede ver a la derecha cómo los diferentes idiomas eligieron el signo del resultado.

dheerosaur
fuente
4

Si xes un número entero y nes una potencia de 2, puede usarlo en x & (n - 1)lugar de x % n.

> -13 & (64 - 1)
51 
cuasimodo
fuente
2

Entonces, parece que si está tratando de modificar los grados (de modo que si tiene -50 grados - 200 grados), querría usar algo como:

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

Me ocupo de lo negativo y negativo tambié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); 
    }
bormat
fuente
1

Esto no es un error, hay 3 funciones para calcular el módulo, puede usar la que se ajuste a sus necesidades (recomendaría usar la función euclidiana)

Truncar la función de la parte decimal

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

Función de parte entera

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

Función 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
fuente
1
En la función euclidiana, la comprobación de m <0 es inútil porque ((esto% n) + n)% n siempre es positivo
bormat
1
@bormat Sí lo es, pero en Javascript %puede devolver resultados negativos (y este es el propósito de estas funciones, solucionarlo)
zessx
usted escribió este [código] Number.prototype.mod = function (n) {var m = ((this% n) + n)% n; retorno m <0? m + Math.abs (n): m; }; [/ code] dame un valor de n donde m es negativo. no tienen valor de n donde m es negativo porque agrega n después del primer%.
bormat
Sin esta comprobación, parseInt(-41).mod(-7)regresaría en -6lugar de 1(y este es exactamente el propósito de la función de parte entera que escribí)
zessx
1
Puede simplificar su función eliminando el segundo módulo Number.prototype.mod = function (n) {var m = this% n; retorno (m <0)? m + Math.abs (n): m; };
bormat
0

Hay un paquete NPM que hará el trabajo por usted. Puede instalarlo con el siguiente comando.

npm install just-modulo --save

Uso copiado del archivo 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

El repositorio de GitHub se puede encontrar a través del siguiente enlace:

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

maartenpaauw
fuente