HOWTO · JavaScript

JavaScript 中的整数除法

在 JavaScript 中选择 Math.trunc 或 Math.floor,以便在负数情况下也能正确计算商和余数。

本页内容

JavaScript 没有 // 整数除法运算符。对于普通 Number 值,若商应向零舍去小数部分,请使用 Math.trunc(dividend / divisor),余数使用 %。只有确实需要向下取整时才选择 Math.floor()。负数时两者不同,因此先确定规则比选择简写更重要。

直接答案:选择截断或向下取整语义

整数除法可能表示两种不同的规则。截断商会移除小数位:Math.trunc(-13 / 5)-2。向下取整商朝负无穷舍入:Math.floor(-13 / 5)-3。正数输入时二者都是 2,所以差异往往到生产环境出现负数才暴露。

当任务需要把带符号的量拆成商和带符号余数时使用截断。对于从零开始分桶或循环索引等商必须始终向下取整的任务,使用向下除法。JavaScript 的 % 返回余数,而不是始终非负的数学模;其符号跟随被除数。MDN 说明了余数运算符与数学模的区别

推荐的 Number 商和余数

对于有限的 Number 输入,Math.trunc% 明确表达商/余数关系,这是一般整数除法任务的推荐做法。计算前检查除数不是零:非零 Number 除以带符号零会产生带符号的 Infinity0 / 0NaN,除数为零的 Number 余数同样是 NaNECMAScript 规范定义了这些除法和余数情形

const dividend = 13;
const divisor = 5;

console.log(`quotient: ${Math.trunc(dividend / divisor)}`);
console.log(`remainder: ${dividend % divisor}`);
quotient: 2
remainder: 3

余数也能验证结果:对整数输入,dividend 等于 divisor * quotient + remainder。此关系有助于校验,但直接用 Math.trunc 计算商比间接推导更清楚地表达所需舍入规则。

负操作数和带符号余数

负操作数是截断和向下取整语义的分界点。对于 -13 / 5,截断得到 -2,向下除法得到 -3% 的结果为 -3,因为 JavaScript 对非零余数保留被除数的符号。两种操作都不会因负输入失败;它们回答的问题不同。

const dividend = -13;
const divisor = 5;

console.log(`truncating quotient: ${Math.trunc(dividend / divisor)}`);
console.log(`floor quotient: ${Math.floor(dividend / divisor)}`);
console.log(`remainder: ${dividend % divisor}`);
truncating quotient: -2
floor quotient: -3
remainder: -3

需要向下除法或模时

若应用在除数为正时需要非负模,应规范化余数,而不要假定 % 已有该行为。与 Math.floor 配合,使两个值遵循相同的向下除法约定。下面示例适用于五项循环,而非所有商/余数任务。

const dividend = -13;
const divisor = 5;
const quotient = Math.floor(dividend / divisor);
const modulo = ((dividend % divisor) + divisor) % divisor;

console.log(`floor quotient: ${quotient}`);
console.log(`non-negative modulo: ${modulo}`);
floor quotient: -3
non-negative modulo: 2

此规范化假设除数为正。若除数可能为负,请单独定义并测试所需约定。

使用 BigInt 处理大型精确整数

Number 使用 IEEE-754 浮点值。在安全整数范围之外,它不能精确表示每个整数,因此依赖 Number 的商或余数前,要用 Number.isSafeInteger 验证精确整数输入。更大的精确整数请始终使用 BigIntBigInt 除法向零截断,其余数也遵循带符号余数的规则。

const dividend = 13n;
const divisor = 5n;

console.log(`quotient: ${dividend / divisor}`);
console.log(`remainder: ${dividend % divisor}`);

try {
  13n / 0n;
} catch (error) {
  console.log(`zero divisor: ${error.name}`);
}

try {
  13n / 5;
} catch (error) {
  console.log(`mixed types: ${error.name}`);
}
quotient: 2
remainder: 3
zero divisor: RangeError
mixed types: TypeError

不要混用 NumberBigInt 操作数;运算前应有意识地转换。BigInt 的零除数会抛出 RangeError,这不同于 Number 的零除数,所以两种情况下输入校验都很有用。MDN 记录了 BigInt 除法、混合类型错误和零除数行为

为什么不用位运算强制转换或 parseInt

你可能见过用 ~~valuevalue | 0 作为紧凑的截断写法。不要把它们用于一般整数除法:位运算符会强制转换为带符号 32 位整数,因而会改变范围外的值。parseInt 用于解析字符串,不是数值舍入 API。MDN 说明了 Math.trunc 及其与位转换的区别。下面的对比展示 32 位转换边界和合法的字符串解析行为。

const value = 2 ** 32 + 1;

console.log(`Math.trunc: ${Math.trunc(value)}`);
console.log(`~~value: ${~~value}`);
console.log(`value | 0: ${value | 0}`);
console.log(`parseInt("13.8", 10): ${parseInt("13.8", 10)}`);
Math.trunc: 4294967297
~~value: 1
value | 0: 1
parseInt("13.8", 10): 13

对数值 Number 使用 Math.trunc,只有输入是待解析文本时才用 parseInt(text, 10)。两种简写都不能修复浮点精度;需要精确的大整数算术时请使用 BigInt

决策摘要和限制

普通 JavaScript 整数除法使用 Math.trunc(a / b)a % b。只有需要向下取整语义时,才使用带规范化模的 Math.floor(a / b)。精确大整数用 BigInt,拒绝零除数,并且不要在同一操作中混用 BigIntNumber。不要把位转换和 parseInt 当作通用替代方案,因为它们解决的是不同问题。