Math.floor与Math.trunc在JavaScript中的区别

83

背景

我正在编写一个函数,该函数接收一个正数,然后将该数字四舍五入到其下面最接近的整数。

我一直在使用Math.floor,但最近我发现了Math.trunc

我知道,在给定正数的情况下,两者都将返回相同的值,并且它们以完全不同的方式工作。 我有兴趣探索这种行为。

问题

  1. 哪个更快?
  2. 我应该使用哪一个?

3
它在所有浏览器中都得到支持,我知道这一点。我只是想知道使用每个选项会产生多大的影响。这就是为什么我创建了这个问题,但社区似乎不喜欢它 :S - Flame_Phoenix
1
如果你指的是 Math.trunc,那么你就错了。Math.trunc 是非常新的函数,在每个浏览器中都不被支持。 - Sebastian Simon
@Xufox:你说得对。我在评估中排除了IE,但是再说一遍,我并不真的关心它,也不认为任何一个理智的人会关心它 :p - Flame_Phoenix
3
我进行了一项快速性能测试,在我的电脑上,每个操作的差异大约为0.00001毫秒。换句话说,这种差别几乎总是实际上没有意义的。 - JJJ
@Juhana 如果我们能把那个作为答案就太好了! - Flame_Phoenix
显示剩余2条评论
3个回答

139

实际上,有很多其他的方法可以从数字中删除小数。但这是可读性和速度之间的权衡。

选择正确的方法取决于你的需要。如果您只需要删除小数,请始终使用trunc()或按位运算符。
从概念上讲,floor()ceil()round()trunc()非常不同。

数学库

您已经了解了这些内容,在标准的非关键代码中始终使用它们。

var v = 3.14; [Math.trunc(v), Math.round(v), Math.floor(v), Math.ceil(v)]
// prints results

对于不同的输入值,您会得到以下结果

 v        t   r   f   c
 3.87 : [ 3,  4,  3,  4]
 3.14 : [ 3,  3,  3,  4]
-3.14 : [-3, -3, -4, -3]
-3.87 : [-3, -4, -4, -3]

Math.trunc()截去(truncate)小数点后的数字。
Math.round()最接近的整数四舍五入。
Math.floor()最接近下方的整数四舍五入。例如:3.5 -> 3 -3.5 -> -4
Math.ceil()最接近上方的整数四舍五入。例如:3.5 -> 4 -3.5 -> -3


但这更有趣:)

二进制操作和位运算符

如果您在代码中查看它们,可能一眼看不出它们的作用,因此在正常代码中不要使用它们。尽管在某些情况下,它们可能会很有用。例如在<canvas/> 中计算坐标。它们速度更快,但有一些限制。

概念上,它们的工作方式如下:

  • 操作数转换为32位带符号整数,因此失去所有小数部分。

ATTENTION:
Numbers with more than 32 bits get their most significant (leftmost) bits discarded and the leftmost bit becomes the new sign bit.

[
  0b011100110111110100000000000000110000000000001, //  15872588537857
~~0b011100110111110100000000000000110000000000001, // -1610588159
             ~~0b10100000000000000110000000000001, // -1610588159
]

按位逻辑运算符

  • 将第一个操作数中的每个比特与第二个操作数中对应的比特配对。(第一位对第一位,第二位对第二位,以此类推。)
  • 对于每一对比特执行运算符,并按位构建结果。

按位移位运算符

  • 这些运算符将要移位的和要将移位的位数作为参数。

截断

然而,在截断时,我们总是使用第二个操作数0、零、false,不会对原始值做任何改变,除了转换为整数,在以下情况下:

~    非      ~~v

|    或     v | 0

<<   左移     v << 0

>> 有符号右移    v >> 0

>>>  无符号右移    v >>> 0

var v = 3.78;
[ ~~v ,  v | 0 ,  v << 0 ,  v >> 0 ,  v >>> 0 ]
// prints these results

 3.78 : [ 3,  3,  3,  3, 3]
 3.14 : [ 3,  3,  3,  3, 3]
-3.74 : [-3, -3, -3, -3, 4294967293]
-3.14 : [-3, -3, -3, -3, 4294967293]

Performance

https://jsperf.com/number-truncating-methods/1

enter image description here


7
非常糟糕的想法。大数字不会按照您想象的那样通过位运算符。尝试使用(Date.now()+0.5)>>>0。这不会给你正确的答案。除非您要进行性能关键的工作并且知道数字始终很小,否则不要使用位运算符来执行向下取整操作。图像处理是一个例子。 - Jason Mitchell
10
@JasonMitchell,实际上这已经在答案中写过了,但为了防止更多人像你一样错过了这一点,我对其进行了强调。感谢您的提示。 - Qwerty
简而言之:使用 trunc ;) - LuckyLuke Skywalker

23
现有的答案已经很好地解释了性能问题。然而,我无法从问题或答案中理解Math.truncMath.floor之间的功能差异,因此我将我的发现放在这个答案中。 Math.trunc将数字向0舍入为整数,而Math.floor将数字向-Infinity舍入为整数。如下图所示,对于正数,方向相同,而对于负数,方向相反。
trunc: towards 0    
floor: towards -Infinity


                   -3      -2     -1      0      1      2      3
-Infinity ... ------+----|--+------+------+------+------+--|----+------ .... Infinity
                         b                                 a    

示例:

var a = 2.3, b = -2.3;
console.log("\t\t\t" + a + "\t\t" + b + "\r\n" + "Math.trunc: " + Math.trunc(a) + "\t\t" + Math.trunc(b) + "\r\n" + "Math.floor: " + Math.floor(a) + "\t\t" + Math.floor(b));

输出:

            2.3     -2.3
Math.trunc: 2       -2
Math.floor: 2       -3

12
如果参数是正数,Math.trunc() 等同于 Math.floor(), 否则 Math.trunc() 等同于 Math.ceil()。 为了性能检查,请使用此函数,最快的函数是 Math.trunc。
var t0 = performance.now();
var result = Math.floor(3.5);
var t1 = performance.now();
console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:', result);
var t0 = performance.now();
var result = Math.trunc(3.5);
var t1 = performance.now();
console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:', result);

结果为:花费了0.0300毫秒生成:3 花费了0.0200毫秒生成:3

因此,如果参数仅为正数,则可以使用最快的计算方式。


@StevenHansen 看起来对我来说是一个有趣的挑战!你认为合理的价值是多少?100万?也许是2百万? - Flame_Phoenix
这里的基准测试只会让答案变得更加复杂,而本身答案是非常简单明了的。 - Steven Vachon
这绝对应该是这个问题的选定答案! - Roman Karagodin
进行一次基准测试并不能足以证明你的观点。你需要生成一个至少包含一千个浮点数的数组,然后使用 floor 和 trunc 两种方法对这个数组进行循环解析,以进行比较。就像这样:https://replit.com/@Freezystem/TruncOrFloor#index.js - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接