JavaScript中将浮点数转换为整数的最佳方法是什么?

32
JavaScript有几种不同的方法可用于将浮点数转换为整数。我的问题是哪种方法能够提供最佳性能、最兼容或被认为是最佳实践?以下是我所知道的一些方法:

有几种不同的方法可用于在JavaScript中将浮点数转换为整数。我的问题是哪种方法能够提供最佳性能、最兼容或被认为是最佳实践?以下是我所知道的一些方法:

var a = 2.5;
window.parseInt(a); // 2
Math.floor(a);      // 2
a | 0;              // 2

我相信还有其他选择。有什么建议吗?


对于建议使用 <code>parseInt</code> 的所有人:请仔细再次阅读原始帖子。 <code>a</code> 是一个数字变量,而不是字符串。 - Thomas
12个回答

41
根据这个网站的说法:

parseInt偶尔被用来将浮点数转换为整数。但是,它非常不适合这个任务,因为如果它的参数是数字类型,则首先会将其转换为字符串,然后解析为数字...

对于将数字舍入到整数的情况,Math.round、Math.ceil和Math.floor更加适合使用...


10

显然,双重按位非是将数字向下取整的最快方法:

var x = 2.5;
console.log(~~x); // 2

以前这里有一篇文章,现在却出现了404错误:http://james.padolsey.com/javascript/double-bitwise-not/

谷歌已经缓存了它:http://74.125.155.132/search?q=cache:wpZnhsbJGt0J:james.padolsey.com/javascript/double-bitwise-not/+double+bitwise+not&cd=1&hl=en&ct=clnk&gl=us

但是Wayback Machine拯救了一天!http://web.archive.org/web/20100422040551/http://james.padolsey.com/javascript/double-bitwise-not/


注意:使用位运算时,必须对可用的数字范围保持敏锐的观察力(这是一个经常犯的错误)。有关更多信息,请参见我的答案 - GitaarLAB

8

以下内容来自Douglas Crockford的《Javascript: The Good Parts》:

Number.prototype.integer = function () {
    return Math[this < 0 ? 'ceil' : 'floor'](this);
}

这样做会向每个数字对象添加一个方法。

然后您可以像这样使用它:

var x = 1.2, y = -1.2;

x.integer(); // 1
y.integer(); // -1

(-10 / 3).integer(); // -3

1
在ES6中,Math.trunc()的作用是这样的。更多信息请参见我的答案 - GitaarLAB

7

'最佳'方式取决于

  • 舍入模式:你期望/要求将浮点数转换为整数时,对于具有小数部分的正数和/或负数使用舍入类型
    常见例子:
    float | trunc | floor |  ceil | near (half up)
    ------+-------+-------+-------+---------------
    +∞    |   +∞  |   +∞  |   +∞  |   +∞  
    +2.75 |   +2  |   +2  |   +3  |   +3
    +2.5  |   +2  |   +2  |   +3  |   +3
    +2.25 |   +2  |   +2  |   +3  |   +2
    +0    |   +0  |   +0  |   +0  |   +0
     NaN  |  NaN  |  NaN  |  NaN  |  NaN
    -0    |   -0  |   -0  |   -0  |   -0
    -2.25 |   -2  |   -3  |   -2  |   -2
    -2.5  |   -2  |   -3  |   -2  |   -2
    -2.75 |   -2  |   -3  |   -2  |   -3
    -∞    |   -∞  |   -∞  |   -∞  |   -∞  
    
    对于浮点数到整数的转换,我们通常期望“截断”(又称“向零舍入”,又称“远离无穷大舍入”)。
    实际上,这只是“截去”浮点数的小数部分。
    大多数技术和(内部)内置方法都是这样处理的。
  • 输入:你的(浮点)数字是如何表示的:
    • String
      通常基数/底数为10(十进制)
    • 浮点('内部')Number
  • 输出:你想对结果值做什么:
    • (中间)输出String(默认基数为10)(在屏幕上)
    • 对结果值执行进一步计算
  • 范围:
    你期望输入/计算结果的数值范围是多少,以及你期望相应的“正确”输出的范围是多少。
只有在回答了这些问题之后,我们才能考虑适当的方法和速度!根据ECMAScript 262规范:Number类型的所有数字在javascript中都以"IEEE 754 Double Precision Floating Point (binary64)"格式表示/存储。因此,整数也以相同的浮点格式表示(作为没有分数的数字)。注意:大多数实现在可能的情况下内部使用更有效(用于速度和内存大小)的整数类型! 由于这种格式存储1个符号位、11个指数位和前53位有效数字(“尾数”),因此我们可以说:只有介于-2的52次方和+2的52次方之间的数字值才能有小数部分。换句话说:所有可表示的正负数字值在2的52次方到(几乎)2的(2的11次方除以2=1024)次方之间都是整数(内部四舍五入,因为没有剩余的比特来表示剩余的小数和/或最不重要的整数位数)。

这里有一个需要注意的地方:
你无法控制 Number 类型在内部转换为浮点数时的舍入模式(舍入模式:IEEE 754-2008 "round to nearest, ties to even"),以及内置的算术操作的舍入模式(舍入模式: IEEE 754-2008 "round-to-nearest")。
例如:
252+0.25 = 4503599627370496.25 被舍入并存储为:4503599627370496
252+0.50 = 4503599627370496.50 被舍入并存储为:4503599627370496
252+0.75 = 4503599627370496.75 被舍入并存储为:4503599627370497
252+1.25 = 4503599627370497.25 被舍入并存储为:4503599627370497
252+1.50 = 4503599627370497.50 被舍入并存储为:4503599627370498
252+1.75 = 4503599627370497.75 被舍入并存储为:4503599627370498
252+2.50 = 4503599627370498.50 被舍入并存储为:4503599627370498
252+3.50 = 4503599627370499.50 被舍入并存储为:4503599627370500

要控制数字的舍入,您的Number需要一个小数部分(并且至少有一个位来表示它),否则ceil/floor/trunc/near将返回您输入的整数。
为了正确地将数字向上舍入/向下舍入/截断到 x 个有效数字的小数位数,我们只关心相应的最低和最高十进制小数值是否在舍入后仍然给我们一个二进制小数值(因此不会被向上或向下取整到下一个整数)。例如,如果您期望对1个有效数字的小数位进行“正确”的四舍五入(对于ceil/floor/trunc),我们需要至少3位(不是4位)来给我们一个二进制小数值:0.10更接近1 /(2 3=8)= 0.125 ,而0.91-1 /(2 3=8)= 0. 875 更接近1
只有在±2(53-3=50)以内的所有可表示值才会在小数点后第一位(值为x.1x.9)之后拥有非零二进制分数。对于2个小数位,使用±2(53-6=47),对于3个小数位,使用±2(53-9=44),对于4个小数位,使用±2(53-13=40),对于5个小数位,使用±2(53-16=37),对于6个小数位,使用±2(53-19=34),对于7个小数位,使用±2(53-23=30),对于8个小数位,使用±2(53-26=27),对于9个小数位,使用±2(53-29=24),对于10个小数位,使用±2(53-33=20),对于11个小数位,使用±2(53-36=17)等等。
在JavaScript中,“安全整数”是一个整数:
  • 它可以被表示为IEEE-754双精度数字,并且
  • 其IEEE-754表示法不能够是将任何其他整数四舍五入以适应IEEE-754表示法的结果 (即使±253(作为完全的2的幂)可以被准确地表示,它也不是一个安全整数,因为在它被舍入以适应最高的53位时,它也可能变成±(253+1))。

这有效地定义了一个范围子集,在 -253+253之间(安全可表示的整数):

  • from: -(253 - 1) = -9007199254740991 (inclusive)
    (a constant provided as static property Number.MIN_SAFE_INTEGER since ES6)
  • to: +(253 - 1) = +9007199254740991 (inclusive)
    (a constant provided as static property Number.MAX_SAFE_INTEGER since ES6)
    Trivial polyfill for these 2 new ES6 constants:

    Number.MIN_SAFE_INTEGER || (Number.MIN_SAFE_INTEGER=
      -(Number.MAX_SAFE_INTEGER=9007199254740991) //Math.pow(2,53)-1
    );
    
自ES6以来,还有一个补充的静态方法Number.isSafeInteger(),用于测试传递的值是否为Number类型,并且是在安全整数范围内的整数(返回布尔值truefalse)。
注意:对于NaNInfinity和显然的String(即使它表示一个数字),也会返回false
Polyfill 示例:
Number.isSafeInteger || (Number.isSafeInteger = function(value){
  return typeof value === 'number' && 
                value === Math.floor(value) &&
                value  <   9007199254740992 &&
                value  >  -9007199254740992;
});

ECMAScript 2015 / ES6提供了一个新的静态方法Math.trunc()
用于将浮点数截断为整数:

返回数字x的整数部分,去除任何小数位。如果x已经是整数,则结果为x。

或者更简单地说(MDN):

与其他三个Math方法不同:Math.floor()Math.ceil()Math.round()Math.trunc()的工作方式非常简单明了:
只需截断小数点及其后面的数字,无论参数是正数还是负数。

我们可以进一步解释(并填充)Math.trunc()如下:

Math.trunc || (Math.trunc = function(n){
    return n < 0 ? Math.ceil(n) : Math.floor(n); 
});

注意,上述 polyfill 的负载 可能 与以下代码相比由引擎更好地预优化:
Math[n < 0 ? 'ceil' : 'floor'](n); 用法: Math.trunc(/* 数字或字符串 */)
输入: (整数或浮点数) 数字 (但是会尝试将字符串转换为数字)
输出: (整数) 数字 (但在字符串上下文中,也会尝试将数字转换为字符串)
范围: -2^52+2^52 (超出此范围我们应该期望出现“舍入误差”(在某些时候科学/指数符号表示),因为我们的 IEEE 754 中的 Number 输入已经失去了小数精度: 因为介于 ±2^52±2^53 之间的数字已经被“内部四舍五入”为整数 (例如,4503599627370509.5 内部已经表示为 4503599627370510),而超过 ±2^53 的整数也会失去精度(2 的幂))。

通过减去除以1的余数Remainder (%)来将浮点数转换为整数:

例如:result = n-n%1(或者n-=n%1
这也应该截断浮点数。由于余数运算符比减法运算符的优先级更高,因此我们实际上得到:(n)-(n%1)
对于正数,很容易看出这会向下取整:(2.5) - (0.5) = 2
对于负数,这会向上取整:(-2.5) - (-0.5) = -2(因为--=+,所以(-2.5) + (0.5) = -2)。

既然输入输出都是Number类型,与 ES6 的 Math.trunc()(或它的 polyfill)相比,我们应该获得相同的有用范围和输出。
注意:虽然我担心(不确定)可能存在差异:因为我们正在对原始数字(浮点数)和第二个派生数字(分数)进行算术运算(在内部使用舍入模式“ nearTiesEven”(又名 Banker's Rounding)),这似乎会引发复合数字表示和算术舍入误差,因此可能仍然返回一个浮点数。


利用位运算将浮点数转换为整数:

这是通过在Number上使用位运算(截断和溢出)将其强制转换为带符号的32位整数值(二进制补码),内部实现的(结果会被转换回只包含整数值的(浮点)Number)。
同样,输入输出都是Number(并且再次从字符串输入自动转换为数字,并从数字输出自动转换为字符串)。
更重要的是(通常被遗忘和未解释):
取决于位运算和数字的符号有用的范围将在以下范围之间受到限制
-2^31+2^31(例如~~numnum|0num>>00+2^32num>>>0)。
这应该通过以下查找表进行进一步澄清(包含所有“关键”示例):
              n             | n>>0或n<<0或  |    n>>>0    | n < 0 ? -(-n>>>0) : n>>>0
                            | n|0或n^0或~~n  |             |
                            | 或n&0xffffffff   |             |
----------------------------+-------------------+-------------+---------------------------
+4294967298.5 = (+2^32)+2.5 |             +2    |          +2 |          +2
+4294967297.5 = (+2^32)+1.5 |             +1    |          +1 |          +1
+4294967296.5 = (+2^32)+0.5 |              0    |           0 |           0
+4294967296   = (+2^32)     |              0    |           0 |           0
+4294967295.5 = (+2^32)-0.5 |             -1    | +4294967295 | +4294967295
+4294967294.5 = (+2^32)-1.5 |             -2    | +4294967294 | +4294967294
       等等...               |         等等...    |      等等... |      等等...
+2147483649.5 = (+2^31)+1.5 |    -2147483647    | +2147483649 | +2147483649
+2147483648.5 = (+2^31)+0.5 |    -2147483648    | +2147483648 | +2147483648
+2147483648   = (+2^31)     |    -2147483648    | +2147483648 | +2147483648
+2147483647.5 = (+2^31)-0.5 |    +2147483647    | +2147483647 | +2147483647
+2147483646.5 = (+2^31)-1.5 |    +2147483646    | +2147483646 | +2147483646
       等等...               |         等等...    |      等等... |      等等...
         +1.5               |             +1    |          +1 |          +1
         +0.5               |              0    |           0 |           0
          0                 |              0    |           0 |           0
         -0.5               |              0    |           0 |           0
         -1.5               |             -1    | +4294967295 |          -1
       等等...               |         等等...    |      等等... |      等等...
-2147483646.5 = (-2^31)+1.5 |    -2147483646    | +2147483650 | -2147483646
-2147483647.5 = (-2^31)+0.5 |    -2147483647    | +2147483649 | -2147483647
-2147483648   = (-2^31)     |    -2147483648    | +2147483648 | -2147483648
-2147483648.5 = (-2^31)-0.5 |    -2147483648    | +2147483648 | -2147483648
-2147483649.5 = (-2^31)-1.5 |    +2147483647    | +2147483647 | -2147483649
-2147483650.5 = (-2^31)-2.5 |    +2147483646    | +2147483646 | -2147483650
       等等...               |         等等...    |      等等... |      等等...
-4294967294.5 = (-2^32)+1.5 |             +2    |          +2 | -4294967294
-4294967295.5 = (-2^32)+0.5 |             +1    |          +1 | -4294967295
-4294967296   = (-2^32)     |              0    |           0 |           0
-4294967296.5 = (-
注意1:最后一列使用(n < 0 ? -(-n>>>0) : n>>>0)扩展范围到0至-4294967295。
注意2:位运算引入了自己的转换开销(s),其严重性与Math方法取决于实际实现,因此在旧的历史浏览器上,位运算可能会更快一些。
显然,如果您的“浮点数”一开始就是一个字符串,parseInt(/*String*/, /*Radix*/)将是将其解析为整数Number的适当选择。
parseInt()也会截断正数和负数。
范围再次限制为IEEE 754双精度浮点数,如上所述的Math方法。

最后,如果您有一个String并期望输出一个String,您也可以截取小数点和小数部分(与IEEE 754双精度浮点数(±2^52)相比,这还给您提供了更大的准确截断范围)!


额外信息:
从上面的信息中,您现在应该知道所有需要知道的内容。

例如,如果您想要远离零四舍五入(又名向正无穷方向舍入),您可以修改Math.trunc() polyfill,例如:

Math.intToInf || (Math.intToInf = function(n){
    return n < 0 ? Math.floor(n) : Math.ceil(n); 
});

5

答案已经给出,但为了更清楚明白,请使用Math库。round、ceil或floor函数。

parseInt用于将字符串转换为整数,这不是需要的。

toFixed用于将浮点数转换为字符串,也不是需要的。

由于Math函数不会执行任何字符串的转换,因此它比其他选择更快,而且其他选择都是错误的。


1
我认为位运算比数学运算快得多,应该始终使用位运算而不是Math.floor和parseInt。 - vsync
2
@vsync:我认为代码的意图应该始终清晰明了,只有在被证明必要时才应使用巧妙的性能技巧。 - AnthonyWJones
@vsync:使用位运算时,必须对可用的数字范围保持敏锐的观察(这是一个经常犯错的错误)。有关更多信息,请参见我的答案 - GitaarLAB

3
你可以使用Number(a).toFixed(0);进行转换;
甚至只需要使用a.toFixed(0)即可。
编辑:这将四舍五入到0位,与截断略有不同。正如其他人建议的那样,toFixed返回一个字符串,而不是原始整数。用于显示目的。
var num = 2.7;  // typeof num is "Number"
num.toFixed(0) == "3"

1
FYI:Number构造函数运行非常慢,所以如果性能很重要的话,你不会想使用它。 - Jason Bunting
好的,知道了!我编辑后添加了第二个 a.toFixed(0); 的形式,因为我记得 JavaScript 原始类型仍然有函数。 - davenpcj
说实话,它们并没有方法。解释器首先将原始值转换为数字对象,然后在其上调用该方法。这对你来说是不可见的。因此,你的第二个示例隐式地执行与第一个示例相同的操作。 :( - Jason Bunting
不仅如此,如果a为2.5,则a.toFixed(0)会将其四舍五入为3。 - Jason Bunting
这是有意为之。http://www.w3schools.com/jsref/jsref_tofixed.asp, 但我会进行编辑以反映这一点。 - davenpcj

2

我做了一个基准测试,在使用Chrome时,当输入已是数字时,最快的是~~numnum|0,速度一半的是Math.floor,最慢的是parseInt,请在这里查看结果。

基准测试结果

编辑:看起来已经有另一个人制作了四舍五入基准测试(更多结果)和其他方法num>>0(与|0一样快)和num - num%1(有时很快)。


2
var i = parseInt(n, 10);

如果您没有指定基数,例如'010'将被视为八进制(因此结果将是8而不是10)。

parseInt(2e20)
200000000000000000000
parseInt(2e21)
2
- Andrew B.

2
使用位运算符。这可能不是将值转换为整数的最清晰方法,但它适用于任何类型的数据。
假设您的函数接受一个参数“value”,并且该函数的工作方式是“value”必须始终是整数(0被接受)。那么以下任何一种方法都可以将“value”分配为整数:
value = ~~(value)
value = value | 0;
value = value & 0xFF;   // one byte; use this if you want to limit the integer to
                        // a predefined number of bits/bytes

最好的部分是,它适用于字符串(例如从文本输入中获取的内容),这些字符串是数字 ~~("123.45") === 123。任何非数字值都会导致结果为0,即,

~~(undefined) === 0
~~(NaN) === 0
~~("ABC") === 0

它也适用于十六进制字符串(带有 0x 前缀)。
~~("0xAF") === 175

我想这里涉及到一些类型转换。我会进行一些性能测试,将它们与 parseInt()Math.floor() 进行比较,但我喜欢额外的便利性,即不会抛出错误,并在非数字情况下得到一个 0


0

这个问题似乎是在特别询问如何将浮点数转换为整数。我的理解是,可以使用 toFixed 来实现这一点。因此...

var myFloat = 2.5;
var myInt = myFloat.toFixed(0);

有人知道Math.floor()Number.toFixed()哪个更快吗?


1
重要提示:.toFixed 的结果始终为 String,而 Math.floor 的结果始终为 Number - Kokizzu

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