JavaScript%(取模)对负数给出负数结果


252

根据Google Calculator (-13) % 6451

根据Javascript(请参阅此JSBin),它是-13

我该如何解决?


这可能只是一个优先问题。你是说(-13) % 64还是-(13 % 64)?就我个人而言,我会采用两种方式之一,只是为了更加清楚。
MatrixFrog 2010年

2
本质上是Java如何用负数进行模数计算的重复项即使这是一个JavaScript问题。
总统詹姆斯·波尔克(James K. Polk)2010年

85
Javascript有时感觉像是一个很残酷的笑话
dukeofgaming 2015年

6
google不会错
caub

10
根本问题在于JS %不是模运算符。这是余数运算符。JavaScript中没有模运算符。因此,公认的答案是正确的方法。
Redu

Answers:


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

摘自本文:JavaScript Modulo Bug


23
我不知道我会称其为“错误”。对于负数,模运算的定义不是很好,并且不同的计算环境对它的处理方式也不同。维基百科有关模运算的文章对此进行了很好的介绍。
丹尼尔·普里登

22
由于它通常被称为“模”,因此看起来似乎很愚蠢,这表明它的行为与其数学定义(参见ℤ/nℤ代数)相同,但事实并非如此。
etienne

7
为什么在加n之前取模?为什么不只加n然后取模呢?
2013年

12
@starwed,如果您不使用此%n,则会失败x < -n-例如,(-7 + 5) % 5 === -2但是((-7 % 5) + 5) % 5 == 3
fadebee 2014年

7
我建议在回答中添加一个答案,即访问该功能的人应使用格式(-13).mod(10)而不是-13%10。这将更加清楚。
Jp_

161

使用Number.prototype是缓慢的,因为每次使用原型方法时,您的数字都会被包装在中Object。代替这个:

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

用:

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

请参阅:http//jsperf.com/negative-modulo/2

比使用原型快约97%。当然,性能对您来说很重要。


1
大提示。我拿了您的jsperf,并与该问题中的其他解决方案进行了比较(但无论如何看来这都是最好的):jsperf.com/negative-modulo/3
Mariano Desanze 2013年

11
微观优化。为此,您必须进行大量的mod计算才能产生任何变化。编写最清晰,最可维护的代码,然后优化以下性能分析。
ChrisV 2014年

我觉得你有你的nS和ms左右在你的第二个例子@StuR走错了路。应该是return ((n % m) + m) % m;
vimist,2015年

这应该是对已接受答案的评论,而不是其本身的答案。
xehpuk '18

5
回答的动机是微优化,是的,但是修改原型是有问题的。首选副作用最少的方法,就是这种方法。
基恩(Keen)'18年

30

%JavaScript中运算符是求余运算符,而不是模运算符(如何负数的处理方式主要区别):

-1 % 8 // -1, not 7


8
应该被称为余运算符,但它:被称为模运算developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/...
大McLargeHuge

15
@DaveKennedy:MDN不是官方语言参考,它是一个社区编辑的站点,有时会出错。规范没有称它为模运算符,据我所知它从来没有(我回到ES3)。它明确表示运算符产生隐式除法的余数,并将其称为“%运算符”。
TJ Crowder

2
如果调用remainder,根据定义,它必须大于0。你不记得高中的除法定理吗?因此,也许您可​​以在这里看看:en.wikipedia.org/wiki/Euclidean_division
Ahmad

19

“ mod”函数返回肯定结果。

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

而且当然

mod(-13,64) // 51

1
MDN不是官方语言参考,它是一个社区编辑的站点,有时会出错。规范没有称它为模运算符,据我所知它从来没有(我回到ES3)。它明确表示运算符产生隐式除法的余数,并将其称为“%运算符”。
TJ Crowder

1
糟糕,您指定的链接实际上是#sec-applying-the-mod-operator在url中引用的:)无论如何,感谢您的注释,我从回答中删除了绒毛,无论如何,这并不是很重要。
Shanimal

3
// @ Shanimal:哈哈!是的 HTML编辑器的错误。规范文本没有。
TJ Crowder

10

接受的答案让我有些紧张,因为它重复使用了%运算符。如果Javascript将来会改变行为,该怎么办?

这是一种不重复使用%的解决方法:

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
如果javascript更改了模运算符以匹配数学定义,则可接受的答案仍然有效。
2013年

20
“如果Javascript将来会改变行为,该怎么办?” -为什么呢?改变这种基本操作符的行为是不可能的。
nnnnnn 2014年

1
+1表示对#answer-4467559精选答案的这种担忧和替代-出于以下四个原因:(1)为什么这样说,是的,“改变这种基本操作的行为不太可能”,但仍应谨慎考虑甚至不需要它。(2)从残破的角度来定义工作选项,虽然令人印象深刻,但至少在第一眼看上去还是令人担忧的,直到显示出来为止(3)我已经很好地验证了这种选择,但我发现更容易遵循快速浏览。(4)小巧:它使用1 div + 1 mul而不是2(mod)divs&我听说过很多早期的硬件,没有好的FPU,乘法速度更快。
Destiny Architect

2
@DestinyArchitect这不是审慎的,没有意义。如果他们要改变余数运算符的行为,则会破坏使用它的程序的范围。那永远不会发生。
神盾局

9
如果行为的-*/;.(),Math.floorfunctionreturn改变?然后,您的代码被严重破坏了。
xehpuk '18

5

尽管它没有按照您预期的方式运行,但这并不意味着JavaScript并未“运行”。这是JavaScript进行模数计算的一种选择。因为,按照定义,任何一个答案都是有意义的。

这个维基百科。您可以在右侧看到不同的语言如何选择结果的符号。


3

如果x是整数并且n是2的幂,则可以使用x & (n - 1)代替x % n

> -13 & (64 - 1)
51 

2

因此,似乎如果您尝试在度数附近进行模数调整(以便拥有-50度至200度的度数),则可能需要使用以下方法:

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

1

我也处理负数n和负数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

这不是错误,有3个函数可以计算模数,您可以使用满足您需要的函数(我建议使用欧几里得函数)

截断小数部分功能

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

整数部分功能

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

欧几里得函数

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
在欧式函数中,检查m <0是无用的,因为((this%n)+ n)%n始终为正
bormat

@bormat是的,但是在Javascript中%可以返回否定结果(这是修复这些功能的目的)
zessx

您编写了此[code] Number.prototype.mod = function(n){var m =((this%n)+ n)%n; 返回m <0?m + Math.abs(n):m; }; [/ code]给我n的一个值,其中m为负。它们不是n的值,其中m为负,因为您在第一个%之后添加n。
bormat '16

如果没有此检查,parseInt(-41).mod(-7)则将返回-6而不是1(这正是我编写的Integer
局部

哈,好吧,你是对的,我所有的道歉,我忘记了负模,我只是在考虑“这个”负数。我可以建议移动Math.abs Number.prototype.mod = function(n){return((this%n)+ Math.abs(n))%n; }; (-41).mod(-7)== 1 //无需parseInt
bormat

0

有一个NPM软件包可以为您完成工作。您可以使用以下命令进行安装。

npm install just-modulo --save

从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

GitHub存储库可通过以下链接找到:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.