取余运算和取模运算如何区分和应用

取余运算和取模运算如何区分和应用

取余运算(remainer operation)和取模运算(modulo operation),很多书籍没有说明它们的区别、也没有说明它们的应用场景。比如《JavaScript 高级程序设计(第 4 版)》直接说:

取模(余数)操作符由一个百分比符号(%)表示。

但实际上,两个概念并不完全一致。严格来说,JavaScript 中的 % 是取余,而不是取模。

有读者可能认为,没必要区分取余和取模。但我想说,区分取余和取模,能帮助你写出更优雅的代码。

以掘金的创作话题轮播为例,它带有灰色进度条,当你左滑或右滑时,话题对应的圆点会变长。

假如让你来实现灰色进度条,需要计算左滑后的下标(prevIndex)和右滑后的下标(nextIndex),你会怎么做呢?

  • 要是你完全不理解取余和取模,为了兼顾左滑、右滑的边界情况,你可能会这样写:
js 复制代码
const prevIndex = currentIndex - 1 < 0 ?
                  len - 1 :
                  currentIndex - 1

const nextIndex = currentIndex + 1 > len - 1 ?
                  0 :
                  currentIndex + 1
  • 要是你只理解取余,不理解取模,你可能会这样写:
js 复制代码
const prevIndex = (currentIndex - 1) % len < 0 ?
                  (currentIndex - 1) + len :
                  currentIndex - 1

const nextIndex = (currentIndex + 1) % len
  • 要是你既理解取余,又理解取模,你可以写出更优雅的代码:(mod 不是 JavaScript 原生函数,需要额外实现)
js 复制代码
const prevIndex = mod(currentIndex - 1, len)
const nextIndex = mod(currentIndex + 1, len)

如果你想知道取余运算和取模运算的区别和应用、以及 mod 函数如何实现,看完这篇文章,你一定有所收获。

我会先给出取余运算和取模运算的计算方式,再用四个例子解释计算方式,接着我会谈一谈什么时候该取余,什么时候该取模,最后我会介绍 JavaScript 中 2 种实现 mod 函数的方法。

计算方式

余数和模的计算方式如下图:

其中 fix 代表向 0 取整,而 floor 代表向负无穷取整。你可能并不理解什么叫作「向 0 取整」,什么叫作「向负无穷取整」,先不要着急,在例子中我会详细讲解。

例子

5 除以 2

把 x = 5,y = 2 代入公式后,你需要计算的是 fix(2.5)floor(2.5)

你可以画出一个右箭头,代表从负无穷指向正无穷的数轴。

找到 2.5 的位置,可以发现 0 和负无穷都在 2.5 的左边。对 2.5 来说,向 0 取整和向负无穷取整,都是往左边取。

所以 fix(2.5) = 2floor(2.5) = 2,把它们代入公式,得到余数和模都是 1。

-5 除以 2

再看 -5 除以 2,继续代入公式画数轴,你需要计算的是 fix(-2.5)floor(-2.5)

找到 -2.5 的位置,可以看到 0 在 -2.5 的右边,而负无穷在 -2.5 的左边。此时向 0 取整就是取 -2.5 右边的整数,向负无穷取整就是取 -2.5 左边的整数。

所以 fix(-2.5) = -2floor(-2.5) = -3。把它们代入公式,得到余数为 -1,模为 1。

5 除以 -2

继续看 5 除以 -2,你需要计算的是 fix(-2.5)floor(-2.5)

之前已经知道 fix(-2.5) = -2floor(-2.5) = -3,把它们代入公式,得到余数为 1,模为 -1。

-5 除以 -2

最后看 -5 除以 -2,你需要计算的是 fix(2.5)floor(2.5)

之前已经知道 fix(2.5) = 2floor(2.5) = 2,把它们代入公示后得到余数和模都是 -1。

余数和模的区别

观察上面的 4 个例子,不难发现,取余运算和取模运算的结果,和 x 和 y 的正负号有关。

  • 当 x 和 y 同为正数、或者同为负数时,x rem y 和 x mod y 的结果一样。
  • 当 x 和 y 符号不一致时,x rem y 和 x mod y 的结果不一样。rem 计算结果符号和 x 一样,mod 计算结果和 y 一样。

实际应用中,y 一般都是正数,因此只需要考虑 x 符号的正负。

  • 当 x 为正数时,此时取余数和取模结果一致,因此最好使用 JavaScript 的 % 运算符。

    例如你想访问数组中的某个元素,又担心下标超出数组的范围,你可以先求下标除以数组长度的余数,把余数作为新下标。

  • 当 x 可能为正数也可能为负数时,此时可以实现一个 mod 函数。

    例如我在文章开头举的例子,prevIndex 可能是一个负数,你可以直接求 prevIndex 除以轮播长度的模。

实现取模函数

取模函数,我知道的有两种实现方法,一种是偏重工程,另一种偏重学术。两者有什么区别呢?

之前提到过,实际应用中,x mod y 时,y 一般是正数。

  • 工程方法追求简单实用,只考虑 y 为正数的情况。

它的写法简单易懂,先求 x 除以 y 的余数,如果结果为负数,就给结果再加上一个 y,保证结果始终为正数。

js 复制代码
function mod(x, y) {
  const res = x % y
  return res < 0 ? res + y : res
}

Ant Design Mobile Swiper ^1^ 源码用的就是这种方法。

  • 学术方法追求逻辑严密,既会考虑 y 为正数,也会考虑 y 为负数。

它的写法如下,我不清楚怎么证明,但代入数字计算,可以验证结果正确。

js 复制代码
function mod(x, y) {
  return ((x % y) + y) % y;
}

MDN ^2^ 和 stackoverflow ^3^ 推荐的就是这种方法。

Footnotes

  1. ant-design-mobile | github

  2. remainder | mdn

  3. JavaScript % (modulo) gives a negative result for negative numbers | stackoverflow

相关推荐
学习使我快乐012 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19952 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈3 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水4 小时前
简洁之道 - React Hook Form
前端
hsling松子4 小时前
使用PaddleHub智能生成,献上浓情国庆福
人工智能·算法·机器学习·语言模型·paddlepaddle
dengqingrui1234 小时前
【树形DP】AT_dp_p Independent Set 题解
c++·学习·算法·深度优先·图论·dp
C++忠实粉丝4 小时前
前缀和(8)_矩阵区域和
数据结构·c++·线性代数·算法·矩阵
ZZZ_O^O5 小时前
二分查找算法——寻找旋转排序数组中的最小值&点名
数据结构·c++·学习·算法·二叉树
CV-King5 小时前
opencv实战项目(三十):使用傅里叶变换进行图像边缘检测
人工智能·opencv·算法·计算机视觉
代码雕刻家6 小时前
数据结构-3.9.栈在递归中的应用
c语言·数据结构·算法