-
Notifications
You must be signed in to change notification settings - Fork 53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
如何优雅地取数值的整数和小数部分 #5
Comments
~~1.233434 // 1
~~-2.75 // -2
~~-0 // 0 |
parseInt(0.0000001); // 1,这也是一个常见的坑
parseInt("0.0000001"); // 0
0.0000001.toString(); // "1e-7" |
嗯嗯,@qgy18 这个我给加上了~ |
这个也是利用位操作转Int32,和|0的原理一样 |
这个真节约不了几个字节哈……想节约还不如换个更好的gzip压缩算法或上Brotli压缩。或者看好图片之类的资源文件,随随便便几百K甚至几M就出去了。这就好比每天跟卖菜小贩讨价还价1个小时节约1毛,买房时不知道多找几个中介,便宜一个点就是几万。😂 我个人认为我们只应该在确定要进行 int32 转型的时候用它,或者按asm.js的约定以 x = x|0 作为类型标注。手动优化而丢失代码逻辑原本的intention是导致可维护性下降的万恶之源。(比如超出32bit或特殊值如Infinity、NaN就统统静悄悄变成了0,而出问题的时候你不知道0到底是从哪里来的 🙃) |
JavaScript的取模运算%并不限于整数运算,可以对浮点数取模。 |
是的,所以现在基本上不推荐使用了,在以前jQuery那个年代,这个用法还挺多见的 |
如果取小数部分是为了做浮点数运算,精度问题是没关系的,如果是为了显示的话,那么应该转字符串,用split也好,用正则表达式也好,都可以的。 |
理论上说这里不存在浮点数『精度』的问题,因为在机器内部本来就不是十进制。长远说我们要等专门的decimal类型(bigint都有了,decimal还会远吗?——还真不好说),短期就得自己走字符串转换下,我一般用 |
如果整数和小数要分开显示的话,我一般是直接用toPrecision或toFixed先将它转字符串,然后split或正则分别取整数和小数部分,这样也不需要处理零头。 |
@akira-cn toFixed转换为字符串的时候就已经失去精度了吧? |
这跟失去精度没关系,toFixed转字符串是为了格式化显示,正常的运算是没问题的,只是浮点数比较不能用===和!==,应该要用下面这个方法: function floatEqual(num, dest) {
return Math.abs(num - dest) < Number.EPSILON;
}
const num = 0.2 + 0.4;
console.log(num, floatEqual(num, 0.6)); // 0.6000000000000001, true |
取整数 const num = 3.11;
num >> 0 // 3
-1 * num >> 0 // -3 |
非要用的话用
|
但是 num-num%1 取到的整数是对的 |
在处理数值的时候,获取浮点数的整数和小数部分,是一种常见的操作,在JavaScript中有许多方法可以达到目的,但也正因为方法众多,所以哪种方法更好,也值得我们仔细研究一番。
取整数
parseInt比较常用来取整数部分,在一些项目中经常能看到:
用parseInt取整数,一般情况下,结果是没问题的,但是如果严格来说,其实parseInt并不是设计用来取整数的。
👉🏻 知识点:
parseInt(string, radix)
这个方法是一个将字符串转换为整数的方法,它有两个参数,第一个参数表示要转换的字符串,如果参数不是一个字符串,则将其转换为字符串。第二个参数是基数即进制,默认为10。所以实际上
parseInt(3.75)
这个代码,会先将3.75转为字符串"3.75"
,然后再将它parseInt
成为3。所以用parseInt方法取整数,有两个不好的地方,一是parseInt这个函数名,看起来就是将字符串转整数的,用在这里不是很适合,另一个是转字符串有点多此一举,而且肯定会带来性能开销,所以使用parseInt虽然方便,但不是最好的办法。
💡补充:
这个toString不仅仅是“多此一举”,还可能导致严重的问题,比如:
这是因为,
0.00000001.toString() === 1e-8
而1000000000000000000000..toString() === 1e+21
。既然parseInt不好用,有经验的同学,会想到用Math的方法来取整,相关的有3个方法,分别是Math.ceil、Math.round和Math.floor。
其中Math.round是四舍五入的,Math.ceil是向上取整,Math.floor是向下取整。
要达到parseInt的结果,我们需要判断数值的符号,如果是负数,要使用Math.ceil,如果是正数,则使用Math.floor:
使用Math.round和Math.ceil实现trunc方法,要比使用parseInt的性能好,因为省去了转字符串。我们可以用jsperf测一下:
结果如下图:
看到使用Math.floor+Math.ceil明显要快。
实际上,在ES2015之后,还提供了原生的
Math.trunc
,我们可以更方便地使用Math.trunc,不用自己使用Math.floor和Math.ceil去实现了:tricky
如果看一些库的代码,你可能会看到这样的取整方式:
这是一种利用位或“|”操作来取整的手段,老司机经常用,我以前也用。
位或运算为什么能达到我们的效果呢,具体可以看ECMA-262文档
对位操作的处理中,第5、6步,会把操作数转为Int32,所以我们就可以利用这个特点来使用“|”操作符了。
不过这么做也是有缺陷的,你发现问题了吗?
👉🏻 冷知识:因为bitwise操作将操作数转为Int32,所以它不能处理超过32位的数值取整,而JavaScript有效整数的范围是53位。
那么用“|”有什么好处呢?如果考虑js文件大小,那么
a|0
与其他方式比较,是最短的方式,所以如果要考虑压缩代码的大小,且明确知道数值范围不会超过32位整数的时候,可以考虑使用这个技巧。取小数
取了整数部分,接下来取小数部分就很简单了:
上面的代码思路就是先用
Math.trunc(num)
取整,然后再与原数相减,就得到了小数部分。但是,我们还有更加简单的办法:
👉🏻 知识点:JavaScript的取模运算%并不限于整数运算,可以对浮点数取模。
所以,直接将原数对1取模,即可获得小数部分!
这是最简单的取小数的方式,然后反过来,还可以倒推出另一种实现trunc取整的方式:
扩展
取小数部分,可以用来实现周期函数,比如实现匀速的js周期动画:
如果我们的周期函数要考虑负数那一半区间,其实fract的方式要修改一下:
这个方式才是正确的周期,它和之前的实现区别是负数区间返回的值不同,前者负数返回的小数部分为负数,这个实现中,如果num是正数,返回num的小数部分,如果num是负数,返回1.0 + num的负数小数部分,这样就保证返回值始终在0.0~1.0的区间内。
好了,关于取整和取小数的讨论就到这里。如果你们还有哪些关于取整和取小数的问题,欢迎在issue中讨论。
The text was updated successfully, but these errors were encountered: