在JavaScript中使用余数进行整数除法?


Answers:


1239

对于一些数字y和一些除数,x将商(quotient)和余数(remainder)计算为:

var quotient = Math.floor(y/x);
var remainder = y % x;

84
%在JavaScript(与许多其他语言不同)中的浮点数上起作用,这也许是不希望的:3.5 % 2求值为1.5。确保按要求处理(parseInt,floor等)

16
在数学中,-4.5的整数部分是-5,因为-5是“仍然可能低于-4.5的最大可能整数”。
艰难的2014年

9
但是,无论您决定对负数做什么,它在商和余数之间都应保持一致。以这种方式使用floor%不一致。使用trunc而不是floor(从而允许负的余数)或使用减法获得余数(rem = y - div * x)。
马克·里德

7
1.如果仍然要计算余数rem,则div无需设下底即可更快地获得商(y - rem) / x。2.通过Donald Knuth的推荐定义(符号匹配除数,不是余数,即欧几里德模数,也不是JavaScript符号匹配除数)的模运算,我们可以在JavaScript中编写为function mod (a, n) { return a % n + (Math.sign(a) !== Math.sign(n) ? n : 0); }
亚伦·曼斯海姆

1
-9 / 2 = -4.5。然后,您取下限-4.5(即-5)。请记住,-5小于-4.5,下限运算被定义为小于给定值的最大整数。
马克·里德

370

我不是位运算符的专家,但是这是获取整数的另一种方法:

var num = ~~(a / b);

这同样适用于负数,而 Math.floor()会朝错误的方向取整。

这似乎也是正确的:

var num = (a / b) >> 0;

84
另一个目的,我刚刚花了最后20分钟试图弄清楚,显然是a/b | 0
BlueRaja-Danny Pflughoeft

18
@ user113716 @BlueRaja按位运算仅对整数类型有意义,而JS(当然)知道这一点。~~intint | 0并且int >> 0不会修改初始参数,而是使解释器将整数部分传递给运算符。
Aleksei Zabrodskii 2012年

15
floor顾名思义,几乎不会朝错误的方向走-但这并不是人们通常想要的方向!
Mark K Cowan

19
那是一个buu buu。a = 12447132275286670000; b = 128 Math.floor(a/b)-> 97243220900677100~~(a/b)-> -1231452688
Mirek Rusin 2014年

7
优先注意。~~(5/2) --> 2一样(5/2)>>0 --> 2,但是~~(5/2) + 1 --> 3,一会儿~~(5/2)>>0 + 1 --> 1~~是一个不错的选择,因为优先级更合适。
2014年

217

我在Firefox上做了一些速度测试。

-100/3             // -33.33..., 0.3663 millisec
Math.floor(-100/3) // -34,       0.5016 millisec
~~(-100/3)         // -33,       0.3619 millisec
(-100/3>>0)        // -33,       0.3632 millisec
(-100/3|0)         // -33,       0.3856 millisec
(-100-(-100%3))/3  // -33,       0.3591 millisec

/* a=-100, b=3 */
a/b                // -33.33..., 0.4863 millisec
Math.floor(a/b)    // -34,       0.6019 millisec
~~(a/b)            // -33,       0.5148 millisec
(a/b>>0)           // -33,       0.5048 millisec
(a/b|0)            // -33,       0.5078 millisec
(a-(a%b))/b        // -33,       0.6649 millisec

以上是每个试验的1000万次试验的结果。

结论:使用(a/b>>0)(或(~~(a/b))(a/b|0))可以使效率提高约20%。也请记住,他们与所有的不一致Math.floor,时a/b<0 && a%b!=0


54
需要注意的是优化整数除法的速度才有意义只有当你正在做的很多。在任何其他情况下,我建议您选择最简单的方法(以您和您的同事认为最简单的方法为准)。
mik01aj 2014年

7
@ m01完全同意-在线方式过多地关注这类东西
JonnyRaa 2014年

2
@ m01但这更难:了解Math.floor其他API函数以及谁知道多少方法,或者了解~(按位非)运算符以及JS中的按位运算如何工作,然后了解双波浪号的作用?
Stijn de Witt

11
好吧,如果您的同事不是在汇编器中编程芯片,他们可能会Math.floor更好地理解。即使没有,这也是可以谷歌搜索的。
mik01aj 2015年

2
@MarkGreen是的,但是只是想这样做并不意味着您应该以怪异的形式编写它,只是因为它恰好是最快的,没有充分的理由-通常,代码清晰通常是您的首要考虑。同样,这些测试在更改语言之后以及在不同的浏览器中可能完全没有意义-无论如何,您都需要进行分析以找出应用程序运行缓慢的原因。除非您已经优化了其他任何事物,否则不太可能成为整数除法!
JonnyRaa

150

ES6引入了新Math.trunc方法。这可以修复@MarkElliot的答案,使其也适用于负数:

var div = Math.trunc(y/x);
var rem = y % x;

请注意,Math与按位运算符相比,方法的优势在于它们可以处理2 31以上的数字。


var y = 18014398509481984; var x = 5; div =?-虫子?
2015年

4
@ 4esn0k这不是错误。您的数字位数太多,在64位二进制格式IEEE 754数字中不能有那么高的精度。例如,18014398509481984 == 18014398509481985
Oriol 2015年

18014398509481984 == 2 ** 54,我特别使用了这个数字,因为它完全以binary64格式表示。答案也正好表示出来
4esn0k

1
我认为选择很简单:您需要支持最多32位带符号的数字吗?使用~~(x/y)。是否需要支持更大的数字(最多签名54位)?Math.trunc如果有,请使用,Math.floor否则使用(对负数正确)。需要支持更大的数字吗?使用一些大数字库。
Stijn de Witt 2015年

4
对于来自Google的红宝石主义者divmod,您可以将其实现为:function divmod(x, y) { var div = Math.trunc(x/y); var rem = x % y; return [div, rem]; }
Alex Moore-Niemi

29
var remainder = x % y;
return (x - remainder) / y;

1
不幸的是,此版本在x = -100时未通过测试,因为它返回-34而不是-33。
塞缪尔

1
那“ var x = 0.3; var y = 0.01;”怎么样??(感谢github.com/JuliaLang/julia/issues/4156#issuecomment-23324163
4esn0k

实际上,@ Samuel具有负值,此方法返回正确的结果,或者至少它返回与使用Math.trunc:) 的方法相同的值。我检查了100,3;-100,3; 100,-3和-100,-3。当然,自您发表评论以来,事情已经改变了很多时间。
Marjan Venema,

19

我通常使用:

const quotient =  (a - a % b) / b;
const remainder = a % b;

它可能不是最优雅的,但是可以工作。


1
好的解决方案,因为它避免了解析或截断float的麻烦。
Dem Pilafian

6
如果同时需要商和余数,则首先计算余数,然后在商表达式中重用该值,即quotient =(a-余数)/ b;
gb96 '19

2
余数= a%b; 商=(a-余数)/ b;
Zv_oDD

16

您可以使用该函数parseInt获取截断的结果。

parseInt(a/b)

要获得余数,请使用mod运算符:

a%b

parseInt对于字符串有一些陷阱,以避免使用以10为底的基数参数

parseInt("09", 10)

在某些情况下,数字的字符串表示形式可能是科学的表示法,在这种情况下,parseInt会产生错误的结果。

parseInt(100000000000000000000000000000000, 10) // 1e+32

此调用将产生1结果。


7
parseInt应尽可能避免使用。这是道格拉斯·克罗克福德(Douglas Crockford)的警告:“如果字符串的第一个字符为0,则该字符串将以8为底,而不是以10为底。在8、8和9底不是数字,因此parseInt(“ 08”)和parseInt (“ 09”)的结果为0。此错误在解析日期和时间的程序中引起问题。幸运的是,parseInt可以采用基数参数,因此parseInt(“ 08”,10)可以生成8。我建议您始终提供基数参数。” archive.oreilly.com/pub/a/javascript/excerpts/...
鲍尔斯

3
在除法运算中,我希望收到一个数字,而不是字符串,但这是一个好主意。
Édipo科斯塔雷博萨斯

2
@Powers,因此添加基数。他没有说parseInt应该避免。只是有一些陷阱要注意。您必须意识到这些事情并做好应对的准备。

2
切勿parseInt使用数字参数调用。parseInt应该解析部分数字的字符串,而不是截断数字。
Oriol

1
仅仅因为事情本来就不是一定要使用的,这并不意味着您不应该这样做。这个答案有效。
fregante

6

JavaScript会根据负数的数学定义,正确计算负数的底限和非整数的余数。

FLOOR被定义为“小于参数的最大整数”,因此:

  • 正数:FLOOR(X)= X的整数部分;
  • 负数:FLOOR(X)= X减1的整数部分(因为它必须比参数小,即,负数更大!)

REMAINDER定义为除法(欧几里德算术)的“剩余”。当被除数不是整数时,商通常也不是整数,即没有余数,但是如果商被强制为整数(当某人尝试获取a的余数或模数时,会发生这种情况浮点数),显然会有一个非整数的“剩余”。

JavaScript确实按预期计算了所有内容,因此程序员必须谨慎地提出适当的问题(人们应该谨慎回答所提出的问题!)Yarin的第一个问题不是“ X除以Y的整数除法是什么”,但是,而是“给定整数的整数倍数”。对于正数,两个答案都相同,但是对于负数,答案并不相同,因为整数除法(除数)将比数字(除数)“乘”另一个(除数)的次数小-1。换句话说,FLOOR将为负数的整数除法返回正确答案,但是Yarin并没有要求!

gammax正确回答,该代码按照Yarin的要求工作。另一方面,塞缪尔(Samuel)是错的,我猜他没有做数学,或者他会看到它确实起作用(而且,他没有说出示例的除数,但是我希望是3):

余数= X%Y = -100%3 = -1

GoesInto =(X-余数)/ Y =(-100--1)/ 3 = -99 / 3 = -33

顺便说一句,我在Firefox 27.0.1上测试了该代码,它按预期工作,具有正数和负数以及非整数值(用于除数和除数)。例:

-100.34 / 3.57:GoesInto = -28,余数= -0.3800000000000079

是的,我注意到那里存在精度问题,但是我没有时间检查它(我不知道这是Firefox,Windows 7还是CPU的FPU出现问题)。但是对于Yarin的问题,该问题只涉及整数,gammax的代码运行完美。


5

Math.floor(operation) 返回操作的舍入值。

第一个问题的示例:

var x = 5;
var y = 10.4;
var z = Math.floor(x + y);

console.log(z);

安慰:

15

第二个问题的示例:

var x = 14;
var y = 5;
var z = Math.floor(x%y);

console.log(x);

安慰:

4



1

Alex Moore-Niemi的回答为:

对于来自Google的Rubyists来说divmod,您可以这样实现它:

function divmod(x, y) {
  var div = Math.trunc(x/y);
  var rem = x % y;
  return [div, rem];
}

结果:

// [2, 33]

2
通常,divmod使用底数除法(Math.floor),它Math.trunc在涉及负数时不同于截断的除法()。这是情况NPM divmod红宝石divmodSWI-Prolog的divmod,或许许多其他的实现了。
Palec'7

截断式分割比底式分割提供更自然的结果,但是兼容性比IMO更为重要。也许也有使用数学运算或性能上的原因进行地板分割。请注意,通常divmod之所以存在,是因为它的执行速度是分别计算两个操作的两倍。在没有这种性能优势的情况下提供此类功能可能会造成混淆。
Palec

1

如果仅用2的幂进行除,则可以使用按位运算符:

export function divideBy2(num) {
  return [num >> 1, num & 1];
}

export function divideBy4(num) {
  return [num >> 2, num & 3];
}

export function divideBy8(num) {
  return [num >> 3, num & 7];
}

(第一个是商,第二个是商)


通常,function divideByPowerOf2(num, exponent) { return [num >> exponent, num & ((1 << exponent) - 1)]; }
Palec

0

您也可以使用三元数来决定如何处理正整数和负整数。

var myInt = (y > 0) ? Math.floor(y/x) : Math.floor(y/x) + 1

如果数字为正数,则表示一切正常。如果数字为负数,则由于Math.floor处理负数的方式而将其加1。


0

这将始终截断为零。不知道是否为时已晚,但是在这里:

function intdiv(dividend, divisor) { 
    divisor = divisor - divisor % 1;
    if (divisor == 0) throw new Error("division by zero");
    dividend = dividend - dividend % 1;
    var rem = dividend % divisor;
    return { 
        remainder: rem, 
        quotient: (dividend - rem) / divisor
    };
}

0

如果需要计算非常大的整数的余数,而JS运行时则不能这样表示(任何大于2 ^ 32的整数都表示为浮点数,因此会失去精度),则需要采取一些技巧。

这对于检查日常生活中许多情况下出现的许多支票号码(银行帐号,信用卡等)尤其重要。

首先,您需要将数字作为字符串(否则,您已经失去了精度,而其余部分则没有意义)。

str = '123456789123456789123456789'

现在,您需要将字符串分割成较小的部分,该部分应足够小,以便将其余部分和一段字符串串联起来可以容纳9位数字。

digits = 9 - String(divisor).length

准备一个正则表达式以分割字符串

splitter = new RegExp(`.{1,${digits}}(?=(.{${digits}})+$)`, 'g')

例如,如果digits为7,则regexp为

/.{1,7}(?=(.{7})+$)/g

它匹配最大长度为7的非空子字符串,其后跟((?=...)是一个正向超前字符)后跟多个是7的倍数的字符。“ g”用于使表达式遍历所有字符串,而不是在第一次匹配时停止。

现在将每个部分转换为整数,并通过以下方式计算余数reduce(将前一个余数-或0-乘以正确的10的幂):

reducer = (rem, piece) => (rem * Math.pow(10, digits) + piece) % divisor

这将因为“减法”余数算法而起作用:

n mod d = (n - kd) mod d

它允许将数字的十进制表示形式的任何“初始部分”用其余数替换,而不会影响最后的余数。

最终代码如下所示:

function remainder(num, div) {
  const digits = 9 - String(div).length;
  const splitter = new RegExp(`.{1,${digits}}(?=(.{${digits}})+$)`, 'g');
  const mult = Math.pow(10, digits);
  const reducer = (rem, piece) => (rem * mult + piece) % div;

  return str.match(splitter).map(Number).reduce(reducer, 0);
}
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.