如何优雅地获取一个数的整数和小数部分? 这个问题看起来简单, 实际上也大有学问. 如果有老司机告诉你, 用 num|0 取整是最简单的方式, 你能反驳 TA, 说出这其中隐藏的坑吗
在处理数值的时候, 获取浮点数的整数和小数部分, 是一种常见的操作, 在 JavaScript 中有许多方法可以达到目的, 但也正因为方法众多, 所以哪种方法更好, 也值得我们仔细研究一番.
如果有想学习编程的初学者, 可来我们的前端直播授课群的哦: 571671034 里面免费送整套系统的前端教程!
取整数
parseInt 比较常用来取整数部分, 在一些项目中经常能看到:
- letnum =3.75;
- console . log ( parseInt ( num ) ) ; // 3
- num = - 3.75 ;
- console . log ( parseInt ( num ) ) ; // -3
用 parseInt 取整数, 结果是没问题的, 但是如果严格来说, 其实 parseInt 并不是设计用来取整数的.
:point_right|type_1_2: 知识点 : parseInt(string, radix) 这个方法是一个将字符串转换为整数的方法, 它有两个参数, 第一个参数表示要转换的字符串, 如果参数不是一个字符串, 则将其转换为字符串 . 第二个参数是基数即进制, 默认为 10.
所以实际上 parseInt(3.75) 这个代码, 会先将 3.75 转为字符串 "3.75" , 然后再将它 parseInt 成为 3.
所以用 parseInt 方法取整数, 有两个不好的地方, 一是 parseInt 这个函数名, 看起来就是将字符串转整数的, 用在这里不是很适合, 另一个是转字符串有点多此一举, 而且肯定会带来性能开销, 所以使用 parseInt 虽然方便, 但不是最好的办法.
那么, 有经验的同学, 会想到用 Math 的方法来取整, 相关的有 3 个方法, 分别是 Math.ceil,Math.round 和 Math.floor.
其中 Math.round 是四舍五入的, Math.ceil 是向上取整, Math.floor 是向下取整.
要达到 parseInt 的结果, 我们需要判断数值的符号, 如果是负数, 要使用 Math.ceil, 如果是正数, 则使用 Math.floor:
- functiontrunc(num){
- if ( num >= 0 ) return Math . floor ( num ) ;
- return Math . ceil ( num ) ;
- }
- console . log ( trunc ( 3.75 ) ) ; // 3
- console . log ( trunc ( - 3.75 ) ) ; // -3
使用 Math.round 和 Math.ceil 实现 trunc 方法, 要比使用 parseInt 的性能好, 因为省去了转字符串. 我们可以用 jsperf 测一下:
结果如下图:
看到使用 Math.floor+Math.ceil 明显要快.
实际上, 在 ES2015 之后, 还提供了原生的 Math.trunc , 我们可以更方便地使用 Math.trunc, 不用自己使用 Math.floor 和 Math.ceil 去实现了:
- console.log(Math.trunc(3.75));// 3
- console . log ( Math . trunc ( - 3.75 ) ) ; // -3
- tricky
如果看一些库的代码, 你可能会看到这样的取整方式:
- letnum =3.75;
- console . log ( num | 0 ) ; // 3
- num = - num ;
- console . log ( num | 0 ) ; // -3
这是一种利用位或 "|" 操作来取整的手段, 老司机经常用, 我以前也用.
这是为什么能达到我们的效果呢, 具体可以看 ECMA-262 文档
对位操作的处理中, 第 5,6 步, 会把操作数转为 Int32, 所以我们就可以利用这个特点来使用 "|" 操作符了.
不过这么做也是有缺陷的, 你发现问题了吗?
:point_right|type_1_2: 冷知识 : 因为 bitwise 操作将操作数转为 Int32, 所以它不能处理超过 32 位的数值取整, 而 JavaScript 有效整数的范围是 53 位.
- constnum=17179869184.89;
- console . log ( num | 0 ) ; // 0
console . log ( Math . trunc ( num ) ) ; // 17179869184
那么用 "|" 有什么好处呢? 如果考虑 JS 文件大小, 那么 a|0 与其他方式比较, 是最短的方式, 所以如果要考虑压缩代码的大小, 且明确知道数值范围不会超过 32 位整数的时候, 可以考虑使用这个技巧.
取小数
取了整数部分, 接下来取小数部分就很简单了:
- functionfract(num){
- return num - Math . trunc ( num ) ;
- }
- console . log ( fract ( 3.75 ) ) ; // 0.75
- console . log ( fract ( - 3.75 ) ) ; // -0.75
上面的代码思路就是先用 Math.trunc(num) 取整, 然后再与原数相减, 就得到了小数部分.
但是, 我们还有更加简单的办法:
:point_right|type_1_2: 知识点 :JavaScript 的取模运算 % 并不限于整数运算, 可以对浮点数取模.
所以, 直接将原数对 1 取模, 即可获得小数部分!
- console.log(3.75%1);// 0.75
- console . log ( - 3.75 % 1 ) ; // -0.75
这是最简单的取小数的方式, 然后反过来, 还可以倒推出另一种实现 trunc 取整的方式:
- functiontrunc(num){
- return num - num % 1 ;
- }
扩展
取小数部分, 可以用来实现周期函数, 比如实现匀速的 JS 周期动画:
- #progress_bar {
- display : inline-block ;
- width : 0 px ;
- height : 20 px ;
- background : red ;
- }
- functionrun(el, duration){
- const startTime = Date . now ( ) ;
- function update ( ) {
- let p = ( Date . now ( ) - startTime ) / duration ;
- p % = 1 ;
- el . style . width = ` ${ 300 * p } px` ;
- requestAnimationFrame ( update ) ;
- }
- update ( ) ;
- }
- const bar = document . getElementById ( 'progress_bar' ) ;
- run ( bar , 3000 ) ;
如果我们的周期函数要考虑负数那一半区间, 其实 fract 的方式要修改一下:
- functionfract(num){
- return num - Math . floor ( num ) ;
- }
这个方式才是正确的周期, 它和之前的实现区别是负数区间返回的值不同, 前者负数返回的小数部分为负数, 这个实现中, 如果 num 是正数, 返回 num 的小数部分, 如果 num 是负数, 返回 1.0 + num 的负数小数部分, 这样就保证返回值始终在 0.0~1.0 的区间内.
- functionfract(num){
- return num - Math . floor ( num ) ;
- }
- console . log ( - 3.75 % 1 ) ; // -0.75
- console . log ( fract ( num ) ) ; // 0.25
好了, 关于取整和取小数的讨论就到这里.
来源: http://www.jianshu.com/p/dbfcb7d08be1