31

有几种不同的方法可以在 JavaScript 中将浮点数转换为整数。我的问题是什么方法可以提供最佳性能、最兼容或被认为是最佳实践?

以下是我知道的几种方法:

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

我敢肯定还有其他人在那里。建议?

4

12 回答 12

39

根据这个网站

parseInt 有时用作将浮点数转换为整数的方法。它非常不适合该任务,因为如果它的参数是数字类型,它将首先转换为字符串,然后解析为数字......

对于将数字舍入为整数,首选 Math.round、Math.ceil 和 Math.floor 之一...

于 2008-09-25T04:01:02.807 回答
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/

于 2010-03-15T04:01:25.760 回答
8

来自 Douglas Crockford 的“Javascript: The Good Parts”:

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

这样做你正在为每个 Number 对象添加一个方法。

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

var x = 1.2, y = -1.2;

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

(-10 / 3).integer(); // -3
于 2011-06-15T18:25:17.173 回答
6

“最佳”方式取决于

  • 舍入模式:对于 具有小数部分的正数和/或负数,您期望/需要哪种类型的舍入(从浮点数到整数) 。 常见示例:

    浮动 | 截断 | 地板 | 细胞 | 近(半高)
    ------+-------+-------+-------+---------------
    +∞ | +∞ | +∞ | +∞ | +∞  
    +2.75 | +2 | +2 | +3 | +3
    +2.5 | +2 | +2 | +3 | +3
    +2.25 | +2 | +2 | +3 | +2
    +0 | +0 | +0 | +0 | +0
     钠 | 钠 | 钠 | 钠 | 钠
    -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 规范: javascript 中的所有数字(类型Number)都表示/存储为:
IEEE 754 Double Precision Floating Point (binary64) ”格式。
因此整数相同的浮点格式表示(作为没有分数的数字)。
注意:大多数实现都尽可能在内部使用更有效的(对于速度和内存大小)整数类型!

由于这种格式存储 1 个符号位、11 个指数位和前 53 个有效位(“尾数”),我们可以说:只有 -和之间的值可以有小数。 换句话说:所有可表示的正负值到几乎)(此时格式称为一天)已经是整数(内部四舍五入,因为没有剩余的位来表示剩余的小数和/或最低有效位整数位)。Number -252+252
Number 2522(211/2=1024) Infinity

还有第一个“陷阱”:
您无法控制Number内置字面量/字符串到浮点转换的 -results 的内部舍入模式(舍入模式:IEEE 754-2008“四舍五入到最接近,关系到偶数” ) 和内置算术运算(舍入模式:IEEE 754-2008 “舍入到最近”)。
例如:四舍五入并存储为:四舍五入并存储为:四舍五入并存储为:四舍五入并存储为:四舍五入并存储为:四舍五入并存储为:四舍五入并存储为:四舍五入并存储为:
252+0.25 = 4503599627370496.254503599627370496
252+0.50 = 4503599627370496.504503599627370496
252+0.75 = 4503599627370496.754503599627370497
252+1.25 = 4503599627370497.254503599627370497
252+1.50 = 4503599627370497.504503599627370498
252+1.75 = 4503599627370497.754503599627370498
252+2.50 = 4503599627370498.504503599627370498
252+3.50 = 4503599627370499.504503599627370500

为了控制四舍五入,您Number需要一个小数部分(以及至少一位来表示),否则 ceil/floor/trunc/near 返回您输入的整数。

为了正确地上限/下限/截断最多 x 个有效小数位的数字,我们只关心相应的最低和最高十进制小数值在舍入后是否仍会给我们一个二进制小数值(因此不会上限或下限为下一个整数)。因此,例如,如果您希望“正确”舍入(对于 ceil/floor/ trunc )最多1
有效小数位(to比 it is to和比it is to更接近。x.1 to x.9
0.11/(23=8)=0.12500.91-1/(23=8)=0.8751

直到所有可表示的值都具有不超过第一个有效十进制小数位的非零二进制小数(值到)。 2位小数、3位小数、4位小数、5位小数、6位小数、7位小数、8位小数、9位小数、10位小数、11位小数等。±2(53-3=50)x.1x.9
±2(53-6=47)±2(53-9=44)±2(53-13=40)±2(53-16=37)±2(53-19=34)±2(53-23=30)±2(53-26=27)±2(53-29=24)±2(53-33=20)±2(53-36=17)

javascript 中的“安全整数”是一个整数:

  • 可以精确表示为 IEEE-754 双精度数,并且
  • 其 IEEE-754 表示不能是对任何其他整数进行四舍五入以适合 IEEE-754 表示的结果
    (即使(作为 2 的精确幂)可以精确表示,但它不是安全整数,因为它之前也可能是它被四舍五入以适应最多 53 个最高有效位)。±253±(253+1)

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

  • from:(含) (从 ES6 开始 作为静态属性提供的常量)-(253 - 1) = -9007199254740991
    Number.MIN_SAFE_INTEGER
  • to: (inclusive) (从 ES6 开始 作为静态属性提供的常量)这两个新的 ES6 常量的简单 polyfill:+(253 - 1) = +9007199254740991
    Number.MAX_SAFE_INTEGER

    Number.MIN_SAFE_INTEGER || (Number.MIN_SAFE_INTEGER=
      -(Number.MAX_SAFE_INTEGER=9007199254740991) //Math.pow(2,53)-1
    );
    


从 ES6 开始,还有一个免费的静态方法Number.isSafeInteger(),用于测试传递的值是否为类型Number并且是安全整数范围内的整数(返回布尔值truefalse)。
注意: 也将返回falseNaNInfinity并且很明显String(即使它代表一个数字)。
填充示例

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()工作方式非常简单明了:
只需截断点及其后面的数字,无论参数是正数还是负数。

我们可以进一步解释(和 polyfill)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(/* Number or String */)
输入:(整数或浮点)Number(但会很乐意尝试将字符串转换为数字)
输出:(整数)Number(但会很乐意尝试在字符串上下文中将数字转换为字符串)
范围-2^52+2^52(超出此范围)我们应该期望“舍入误差”(在某些时候是科学/指数表示法)简单明了,因为我们Number在 IEEE 754 中的输入已经失去了小数精度:因为 to 之间±2^52的数字±2^53已经是内部舍入的整数(例如4503599627370509.5,内部已经表示为4503599627370510)并且超出±2^53整数也失去精度(2的幂))。


浮点数到整数的转换通过减去一个除法的余数( %) 1

示例:(result = n-n%1n-=n%1
这也应该截断浮点数。由于余数运算符的优先级高于减法,我们有效地得到:(n)-(n%1)
对于正数,很容易看出这限制了值:(2.5) - (0.5) = 2
对于负数,这限制了值:((-2.5) - (-0.5) = -2因为--=+so (-2.5) + (0.5) = -2)。

由于输入输出是与 ES6 (或它的 polyfill)相比,Number我们应该得到相同的有用范围和输出。注意:我担心很难(不确定)可能存在差异:因为我们正在对原始数字(浮点数)和第二个派生数字(分数)进行算术运算(内部使用舍入模式“nearTiesEven”(又名银行家舍入) ) 这似乎会导致复合数字表示和算术舍入错误,因此可能最终返回一个浮点数..Math.trunc()


通过(ab-)使用按位运算将浮点数转换为整数:

这通过对 a使用按位运算内部强制将(浮点)Number转换(截断和溢出)转换为有符号的 32 位整数值(二进制补码) (并将结果转换回保持的(浮点)只是整数值)。 NumberNumber

同样,输入输出Number(以及从字符串输入到数字和数字输出到字符串的无声转换)。

更重要的强硬(通常被遗忘且未解释):
根据按位运算和数字的符号有用范围将被限制在:
-2^31to +2^31(like ~~numor num|0or num>>0 0to +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
       等等... | 等等... | 等等... | ETC...
+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
       等等... | 等等... | 等等... | ETC...
         +1.5 | +1 | +1 | +1
         +0.5 | 0 | 0 | 0
          0 | 0 | 0 | 0
         -0.5 | 0 | 0 | 0
         -1.5 | -1 | +4294967295 | -1
       等等... | 等等... | 等等... | ETC...
-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
       等等... | 等等... | 等等... | ETC...
-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 = (-2^32)-0.5 | 0 | 0 | 0
-4294967297.5 = (-2^32)-1.5 | -1 | +4294967295 | -1
-4294967298.5 = (-2^32)-2.5 | -2 | +4294967294 | -2

0注 1:最后一列的范围已扩展到-4294967295using (n < 0 ? -(-n>>>0) : n>>>0)
注意 2:按位引入了它自己的转换开销(s)(严重程度与Math实际实现有关,因此按位可能更快(通常在较旧的历史浏览器上))。


显然,如果您的“浮点”数是 aString开头,
parseInt(/*String*/, /*Radix*/)则将其解析为 integer 是一个合适的选择Number
parseInt()也会截断(对于正数和负数)。
范围再次被限制为 IEEE 754 双精度浮点,如上面针对Math方法所解释的。

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


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

例如,如果您希望从零舍入(也就是向无穷大舍入),您可以修改Math.trunc()polyfill,例如

Math.intToInf || (Math.intToInf = function(n){
    return n < 0 ? Math.floor(n) : Math.ceil(n); 
});
于 2016-03-21T13:05:28.657 回答
5

答案已经给出,但只是为了清楚。

为此使用数学库。圆形、天花板或地板功能。

parseInt 用于将字符串转换为 int,这不是这里需要的

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

由于数学函数不会对字符串进行任何转换或从字符串进行任何转换,因此它将比任何其他错误的选择都更快。

于 2008-09-25T08:24:52.593 回答
3

您可以使用 Number(a).toFixed(0);

甚至只是 a.toFixed(0);

编辑:

这是四舍五入到 0 位,与截断略有不同,正如其他人所建议的那样, toFixed 返回一个字符串,而不是原始整数。用于显示目的。

var num = 2.7;  // typeof num is "Number"
num.toFixed(0) == "3"
于 2008-09-25T04:00:09.377 回答
2
var i = parseInt(n, 10);

如果您不指定基数值 like'010'将被视为八进制(因此结果将8不是10)。

于 2008-09-25T04:08:31.883 回答
1

使用位运算符。它可能不是转换为整数的最清晰方法,但它适用于任何类型的数据类型。

假设您的函数接受一个参数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()and进行比较Math.floor(),但我喜欢额外的便利,即不会Errors被抛出并获得0非数字的 a

于 2011-09-07T16:19:15.173 回答
1

所以我做了一个基准,Chrome当输入已经是一个数字时,最快的是~~numand num|0, half speed: Math.floor,最慢的会在这里parseInt看到

基准测试结果

编辑:似乎已经有另一个人做了舍入基准(更多结果)和其他方法:(num>>0尽可能快|0)和num - num%1(有时快)

于 2015-02-23T05:18:42.727 回答
0

这个问题似乎是专门询问从浮点数转换为整数的问题。我的理解是这样做的方法是使用toFixed. 所以...

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

有谁知道是否Math.floor()比性能更高或更低Number.toFixed()

于 2008-09-25T04:55:47.823 回答
-1

你也可以这样做:

var string = '1';
var integer = a * 1;
于 2008-09-25T22:40:02.053 回答
-4

parseInt() 可能是最好的一个。a | 0没有做你真正想要的(如果 a 是一个未定义或空值,它只是分配 0,这意味着一个空对象或数组通过了测试),并且 Math.floor 通过某种类型的诡计工作(它基本上调用 parseInt()在后台)。

于 2008-09-25T03:56:49.850 回答