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_INTEGER
和Number.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_VALUE
和Number.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')
复制代码