lodash 取整函数 ceil round floor

ceil函数是一个向上取整函数,round四舍五入函数,floor向下取整函数。本文涉及到原始类型Number以及JavaScript的浮点数运算问题。

Math静态方法不同的是,基于createRound实现的这三个函数可以通过第二个参数precision对取整的精度进行控制,当不传precision参数的时候,相当于直接调用Math上的静态方法。

createRound

根据传入的Math上静态方法的函数名,动态的生成对应的函数,其实现如下:

/**
 * Creates a function like `round`.
 *
 * @private
 * @param {string} methodName The name of the `Math` method to use when rounding.
 * @returns {Function} Returns the new round function.
 */
function createRound(methodName) {
  // 默认调用Math上的方法
  const func = Math[methodName]
  return (number, precision) => {
    precision = precision == null ? 0 : (precision >= 0 ? Math.min(precision, 292) : Math.max(precision, -292))
    // 需要根据精度向上取整
    if (precision) {
      // Shift with exponential notation to avoid floating-point issues.
      // See [MDN](https://mdn.io/round#Examples) for more details.
      let pair = `${number}e`.split('e')
      const value = func(`${pair[0]}e+${pair[1] + precision}`)
      pair = `${value}e`.split('e')
      return +`${pair[0]}e${+pair[1] - precision}`
    }
    // 不需要
    return func(number)
  }
}
复制代码

其中e为科学记数法,举个例子:

  • 1e+2 就等于1*(10*10)=100,等同于1e2
  • 100e-2 就等于 100/(10*10)=1

在不传入precision参数的情况下,该函数等于:

function createRound(methodName) {
  const func = Math[methodName]
  return (number) => {
    return func(number)
  }
}

复制代码

相当于直接调用Math[methodName]

因此我们直接看precision相关的逻辑:

precision = precision == null ? 0 : (precision >= 0 ? Math.min(precision, 292) : Math.max(precision, -292))
复制代码

这里对precision的最大和最小值进行了约定,即最大292,Math.pow(10, 292),最小-292,Math.pow(10, -292)。这里的precision是后续科学计数法e后面的值

那么292是如何得出来的呢?

我们先从SAFE_INTEGER说起

JavaScript中,存在两个值Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER。大于Math.pow(2, 53) - 1(9007199254740991)与小于-Math.pow(2, 53) - 1(-9007199254740991)无法正确展示。可以在浏览器中log一下9007199254740993看浏览器返回什么

console.log(9007199254740993)
9007199254740992
复制代码

如果想要表示大于Number.MAX_SAFE_INTEGER的数,可以使用BigInt,比如9007199254740993n就可以正确表示

console.log(9007199254740993n)
9007199254740993n
复制代码

在JavaScript中可表示的最大数值是多少呢,Number.MAX_VALUENumber.MIN_VALUE。这两个数的值大约是1.7976931348623157e+308-1.7976931348623157e+308

到这里你可能就想到了,用JavaScript可表示的最大数值除以我们常用的安全数值,正是292

console.log(Number.MAX_VALUE/Number.MAX_SAFE_INTEGER)
1.99584030953472e+292
复制代码

接着看后续的代码

let pair = `${number}e`.split('e')
const value = func(`${pair[0]}e+${pair[1] + precision}`)
pair = `${value}e`.split('e')
return +`${pair[0]}e${+pair[1] - precision}`
复制代码

这里的逻辑就比较好理解了,如果precision是正数,利用科学记数法,先把value值扩大10的precision次方,调用Math的对应函数,然后再把值缩小10的precision次方。如果是负数,则相反。这里也提出了一种解决js浮点数运算问题的方案,即使用科学记数法。

floating-point 问题是如何产生的?

计算机使用二进制,而二进制的浮点数并不能精确的表示诸如0.1,0.2,0.3这样的数字。类似于10进制的1/3=0.3333333…一样。不光Javascript有这个问题,很多编程语言都有这个问题。
更多的内容可以参考:

Is floating point math broken?

What Every Programmer Should Know About Floating-Point Arithmetic

ceil向上取整函数

/**
 * Computes `number` rounded up to `precision`. (Round up: the smallest integer greater than or equal to a given number.)
 * 向上取整,并可以根据 precision 参数对小数位或整数位向上取整
 * 
 * @since 3.10.0
 * @category Math
 * @param {number} number The number to round up.
 * @param {number} [precision=0] The precision to round up to.
 * @returns {number} Returns the rounded up number.
 * @example
 *
 * ceil(4.006)
 * // => 5
 *
 * ceil(6.004, 2)
 * // => 6.01
 *
 * ceil(6040, -2)
 * // => 6100
 */
const ceil = createRound('ceil')
复制代码

floor向下取整函数

/**
 * Computes `number` rounded down to `precision`.
 *
 * @since 3.10.0
 * @category Math
 * @param {number} number The number to round down.
 * @param {number} [precision=0] The precision to round down to.
 * @returns {number} Returns the rounded down number.
 * @example
 *
 * floor(4.006)
 * // => 4
 *
 * floor(0.046, 2)
 * // => 0.04
 *
 * floor(4060, -2)
 * // => 4000
 */
const floor = createRound('floor')
复制代码

round 四舍五入

/**
 * Computes `number` rounded to `precision`.
 *
 * @since 3.10.0
 * @category Math
 * @param {number} number The number to round.
 * @param {number} [precision=0] The precision to round to.
 * @returns {number} Returns the rounded number.
 * @example
 *
 * round(4.006)
 * // => 4
 *
 * round(4.006, 2)
 * // => 4.01
 *
 * round(4060, -2)
 * // => 4100
 */
const round = createRound('round')
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享