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

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

取余运算(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

相关推荐
dae bal几秒前
关于RSA和AES加密
前端·vue.js
柳杉4 分钟前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
wwww.bo11 分钟前
机器学习(决策树)
算法·决策树·机器学习
辞--忧16 分钟前
深入浅出决策树
算法·决策树·机器学习
Y2003091616 分钟前
决策树总结
算法·决策树·机器学习
lynn8570_blog17 分钟前
低端设备加载webp ANR
前端·算法
LKAI.38 分钟前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi
刺客-Andy1 小时前
React 第七十节 Router中matchRoutes的使用详解及注意事项
前端·javascript·react.js
图灵学术计算机论文辅导1 小时前
论文推荐|迁移学习+多模态特征融合
论文阅读·人工智能·深度学习·计算机网络·算法·计算机视觉·目标跟踪
前端工作日常1 小时前
我对eslint的进一步学习
前端·eslint