如何在JavaScript中执行整数除法,并单独获取余数

1353
在JavaScript中,我如何获得以下内容:
1. 一个给定整数被另一个整数整除的次数? 2. 余数?
21个回答

4

Alex Moore-Niemi的评论作为答案:

如果你是从 Google 搜索 divmod 而来的 Ruby 开发者,可以按照以下方式实现:

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

结果:

// [2, 33]

3
通常情况下,divmod 函数使用向下取整的除法(Math.floor),当涉及负数时与截断除法(Math.trunc)不同。这适用于NPM divmodRuby divmodSWI-Prolog divmod和很可能许多其他实现也是如此。 - Palec
1
截断除法比向下取整除法给出的结果更自然,但是在我看来,兼容性更重要。也许,使用向下取整除法有数学或性能上的原因。请注意,通常情况下,divmod 存在是因为它的执行速度是分别计算这两个操作的两倍。提供这样一个没有这种性能优势的函数可能会令人困惑。 - Palec

2
如果需要计算非常大的整数的余数,JS运行时不能将其表示为数字(任何大于2^32的整数都表示为浮点数,因此会失去精度),那么就需要一些技巧。
这对于检查许多情况下的校验位特别重要,这些情况存在于我们日常生活的许多实例中(银行账号、信用卡等)。
首先,您需要将数字作为字符串(否则您已经失去了精度,余数就没有意义)。
str = '123456789123456789123456789'

现在你需要将字符串分成更小的部分,足够小,以便于任何剩余部分和一个字符串片段的连接可以适合9个数字。

digits = 9 - String(divisor).length

准备一个正则表达式来拆分字符串

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

例如,如果 digits 是7,则正则表达式为
/.{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);
}

2
在计算机科学文献和编程语言中,对原始函数
div(计算除法的)和mod(计算除法的余数)有几种可能的定义,满足以下约束条件:

  • Number.isInteger(div(x, y))
  • x === div(x, y) * y + mod(x, y)
  • Math.abs((mod(x, y)) < Math.abs(y)

这些定义在计算机科学文献和编程语言中的常见用法基于

  • 截断除法:

    function div(x, y) {
      return Math.trunc(x / y);
    }
    
    function mod(x, y) {
      return x % y;
    }
    
  • 向下取整除法:

    function div(x, y) {
      return Math.floor(x / y);
    }
    
    function mod(x, y) {
      return ((x % y) + y) % y;
    }
    
  • 欧几里得除法:

    function div(x, y) {
      return Math.sign(y) * Math.floor(x / Math.abs(y));
    }
    
    function mod(x, y) {
      const z = Math.abs(y);
      return ((x % z) + z) % z;
    }
    
此外,
- 截断除法具有以下特性:mod(x, y) * x >= 0; - 地板除法具有以下特性:mod(x, y) * y >= 0; - 欧几里得除法具有以下特性:mod(x, y) >= 0
因此,
- 如果 x >= 0y > 0,则截断、地板和欧几里得除法相同; - 如果 x >= 0y < 0,则截断和欧几里得除法相同; - 如果 x <= 0y > 0,则地板和欧几里得除法相同; - 如果 x <= 0y < 0,则截断和地板除法相同。
欧几里德除法的选择被推荐用于定义函数
和,而不是截断除法和向下取整除法,根据Raymond Boute的论文《欧几里德定义的函数div和mod》:
在这篇论文中,我们阐明了各种定义之间的差异,特别是基于截断除法(T-定义)和基于向下取整除法(F-定义)的定义,这些定义由Knuth提出。我们还提出了另一种定义,我们称之为欧几里德定义,因为它基于欧几里德定理(E-定义)。这种替代方案在文献中很少讨论,然而经过更深入的分析,它在正规性和有用的数学性质方面都具有优势,无论是在理论上还是在实际使用中。在我们遇到需要div-mod函数对的各种代表性应用领域中,欧几里德定义通常是最直接的选择。

2
我们可以采用以下方法。
quotient = dividend / divisor | 0;

我们可以通过取模运算符来得到提醒的方式。
remainder = dividend % divisor;

1

如果你只是按二次幂进行除法运算,你可以使用位运算符:

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

1
function integerDivison(dividend, divisor) {

    this.Division = dividend/divisor;
    this.Quotient = Math.floor(dividend/divisor);
    this.Remainder = dividend%divisor;
    this.calculate = () => {
        return {Value:this.Division, Quotient:this.Quotient, Remainder:this.Remainder};
    }
}

var divide = new integerDivison(5, 2);
console.log(divide.Quotient)     // To get the quotient of two values
console.log(divide.division)     // To get the floating division of two values
console.log(divide.Remainder)    // To get the remainder of two values
console.log(divide.calculate())  // To get the object containing all the values

1
如果你需要计算一些非常大的数的商,你可以使用以下方法:
Math.trunc((x/y) + (Number.EPSILON * (2 ** Math.ceil(Math.log2(Math.abs(x/y))))) * Math.sign(x/y))

注意:这仅适用于情况,其中xy值,即被除数除数,在进行舍入后仍然准确表示,以便在它们大于Number.MAX_SAFE_INTEGER时作为整数工作。

例如,如果我们有:

x = 45000000000000000000000000000 = 4.5e+28
y =   500000000000000000000000000 =   5e+26

然后,此页面上给出的答案会为您提供:
89.99999999999999: x/y
90: Math.trunc((x/y) + (Number.EPSILON * (2 ** Math.ceil(Math.log2(Math.abs(x/y))))) * Math.sign(x/y))
89: Math.floor(x/y)
89: ~~(x/y)
89: (x/y)>>0
89: x/y|0
89: (x-(x%y))/y

正确答案是90,所以你可以看到,我给出的方程式是唯一能提供正确答案的方程式。
这个方程式也适用于负数结果。如果我们让x为负数,那么我们会得到:
-89.99999999999999: x/y
-90: Math.trunc((x/y) + (Number.EPSILON * (2 ** Math.ceil(Math.log2(Math.abs(x/y))))) * Math.sign(x/y))
-90: Math.floor(x/y)
-89: ~~(x/y)
-89: (x/y)>>0
-89: x/y|0
-89: (x-(x%y))/y

只有那个方程和 Math.floor() 给出了正确的答案。
而且,为了确认一些不同的值,它们给出了稍微大一点的结果:
x = -45000000000000000000000000 = -4.5e+25
y =    500000000000000000000000 =    5e+23

我们得到:
-90.00000000000001: x/y
-90: Math.trunc((x/y) + (Number.EPSILON * (2 ** Math.ceil(Math.log2(Math.abs(x/y))))) * Math.sign(x/y))
-91: Math.floor(x/y)
-90: ~~(x/y)
-90: (x/y)>>0
-90: x/y|0
-90.00000000000001: (x-(x%y))/y

在这种情况下,Math.floor()(x-(x%y))/y都会失败,这意味着,虽然可能不快也不美观,但本答案中给出的代码是唯一能在所有情况下给出正确结果的方法,前提是被除数和除数能够准确表示。(或者至少是我所知道的所有情况。)
如果你想知道如何获得大数的正确余数,请参考: 补充:如果你只处理正数,那么可以简化为以下代码:
Math.trunc((x/y) + (Number.EPSILON * (2 ** Math.ceil(Math.log2(x/y)))))

0
你可以使用三元运算符来决定如何处理正负整数值。
var myInt = (y > 0) ? Math.floor(y/x) : Math.floor(y/x) + 1

如果这个数是正数,一切都好。如果这个数是负数,它会加1,因为Math.floor函数对负数的处理方式。

0

计算页面数量可以在一步完成:

Math.ceil(x/y)

2
我看不出这如何提供余数。 - Paul Rooney
1
你说的“页面数量”是什么意思?这是一个虚假的回答吗? - Peter Mortensen

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接