JavaScript的最高整数值是多少,数字不会失去精度?

1093

这是由语言定义的吗?有定义的最大值吗?在不同的浏览器中是否不同?


6
你不需要依赖 JS 的限制,可以使用像 https://github.com/MikeMcl/big.js 这样的库,例如在这里查看其可靠性测试。 - Dmitri Zaitsev
4
你能在 big.js 中使用的最大整数值是多少? - George
1
@DmitriZaitsev 我们不再需要依赖外部库(至少在某些浏览器上)。1n << 10000n 是一个非常非常大的整数,不会失去任何精度,也不需要任何依赖项(不用说,远远没有达到极限)。 - Amadan
1
@DmitriZaitsev 注意后缀nBigInt类是ES2020规范草案的一部分,已经在大多数浏览器中实现; 您可以尝试在Chrome或Firefox中评估它,而无需使用外部库,并获得3011位数字的BigInt - Amadan
2
@DmitriZaitsev:是的,它只适用于整数。这个问题是关于整数的。 - Amadan
显示剩余7条评论
21个回答

978
JavaScript有两种数字类型:NumberBigInt
最常用的数字类型Number是64位浮点数IEEE 754数。该类型的最大精确整数值是Number.MAX_SAFE_INTEGER,它是:
  • 253-1,或
  • +/- 9,007,199,254,740,991,或
  • 九千零七万亿一千九百九十九亿二千五百四十七万四千九百九十一
将其放入视角:一千万亿字节是一拍(或一千万兆字节)。
在这个上下文中,“安全”指的是能够准确地表示整数并正确比较它们。
从规范中可知:

请注意,所有正的和负的整数,其大小不超过 2^53,都可以用Number类型表示(确实,整数 0 有两个表示,+0 和 -0)。

要安全地使用比此更大的整数,您需要使用没有上界的BigInt
请注意,按位运算符和移位运算符操作32位整数,在这种情况下,最大的安全整数是231-1,即2,147,483,647。

const log = console.log
var x = 9007199254740992
var y = -x
log(x == x + 1) // true !
log(y == y - 1) // also true !

// Arithmetic operators work, but bitwise/shifts only operate on int32:
log(x / 2)      // 4503599627370496
log(x >> 1)     // 0
log(x | 1)      // 1


关于数字9,007,199,254,740,992的技术说明:存在该值的精确IEEE-754表示,你可以将该值赋给变量并从中读取,因此对于在不大于该值的整数领域中非常小心地选择应用程序,你可以将其视为最大值。

在一般情况下,必须将这个IEEE-754值视为不精确的,因为它是模棱两可的,无法确定它是否编码了逻辑值9,007,199,254,740,992或9,007,199,254,740,993。


77
这似乎是正确的,但是否有一个类似于C语言的MAX_INT或Java的Integer.MAX_VALUE的定义呢? - TALlama
53
4294967295 === Math.pow(2,32) - 1; - coolaj86
13
我们需要使用哪个最小和最大的整数才能保证精确度? - Pacerier
41
值得注意的是,在 JavaScript 中实际上不存在 (int) 类型。每个 Number 实例都是 (float) 或 NaN。 - Beetroot-Beetroot
60
这里的9007199254740992并不是真正的最大值,因为它假设了最后一位是零,所以失去了1个比特位的精度。真正的安全数是9007199254740991(Number.MAX_SAFE_INTEGER)。 - Willem D'Haeseleer
显示剩余22条评论

495

>= ES6:

Number.MIN_SAFE_INTEGER;
Number.MAX_SAFE_INTEGER;

<= ES5

参考此处

Number.MAX_VALUE;
Number.MIN_VALUE;

console.log('MIN_VALUE', Number.MIN_VALUE);
console.log('MAX_VALUE', Number.MAX_VALUE);

console.log('MIN_SAFE_INTEGER', Number.MIN_SAFE_INTEGER); //ES6
console.log('MAX_SAFE_INTEGER', Number.MAX_SAFE_INTEGER); //ES6


23
我已经编辑了问题,更准确地表示我想要的是最大整数值,而不仅仅是最大数字值。对于造成的困惑,我很抱歉。 - TALlama
5
所有浏览器返回的结果都保证相等吗? - Pacerier
7
请注意,Number.MIN_VALUE是最小的可能的_正数_。最小的值(即小于任何其他值的值)可能是-Number.MAX_VALUE - Michael Scheper
37
ES6 引入了 Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER - superlukas
2
那么,在这种情况下,我们应该因为回答对于更新后的问题是错误的而将其向下投票,还是因为Peter Baily在回答时是正确的而将其保留? - rocketsarefast
显示剩余5条评论

119

这是因为Number存储为52位尾数的浮点数,因此它等于2的53次方,即9,007,199,254,740,992。

最小值为-2的53次方。

这导致了一些有趣的事情发生。

Math.pow(2, 53) == Math.pow(2, 53) + 1
>> true

这也可能很危险哦 :)

var MAX_INT = Math.pow(2, 53); // 9 007 199 254 740 992
for (var i = MAX_INT; i < MAX_INT + 2; ++i) {
    // infinite loop
}

更多阅读:http://blog.vjeux.com/2010/javascript/javascript-max_int-number-limits.html


1
虽然在合理的时间内永远无法完成那个for循环,但您可能希望说i += 1000000000 - ninjagecko
4
@ninjagecko,他从MAX_INT开始,所以循环结束的位置就在那里。另外,使用i+= 1000000000将使其不再是无限循环。试试看。 - Ted Bigham
@TedBigham:啊,糟糕,我太快了。感谢你两次纠正我。 - ninjagecko
1
请查看Jimmy关于9,007,199,254,740,991而不是9,007,199,254,740,992的论点此处。结合我的后续问题,这似乎很有说服力。 - T.J. Crowder

65

在JavaScript中,有一个称为Infinity的数字。

例如:

(Infinity>100)
=> true

// Also worth noting
Infinity - 1 == Infinity
=> true

Math.pow(2,1024) === Infinity
=> true

对于某些关于这个主题的问题,这可能已经足够了。


27
有些东西告诉我,无限大不能被归类为整数 :) - devios1
8
当你寻找最小值时,初始化一个“min”变量就足够好了。 - djjeck
9
请注意,“Infinity - 1 === Infinity”的意思是无穷大减去1等于无穷大。 - H.Wolper
2
也就是说 (Infinity<100) => false 和 Math.pow(2,1024) === Infinity - Sijav
6
值得注意的是,它也可以处理负无穷大。因此 1 - Infinity === -Infinity - dmccabe
显示剩余4条评论

47
许多早期的答案已经展示了9007199254740992 === 9007199254740992 + 1true,以验证9,007,199,254,740,991是最大且安全的整数。但是如果我们继续进行累加会怎样呢:
input: 9007199254740992 + 1  output: 9007199254740992  // expected: 9007199254740993
input: 9007199254740992 + 2  output: 9007199254740994  // expected: 9007199254740994
input: 9007199254740992 + 3  output: 9007199254740996  // expected: 9007199254740995
input: 9007199254740992 + 4  output: 9007199254740996  // expected: 9007199254740996

我们可以看到在大于 {{9,007,199,254,740,992}} 的数字中,只有偶数是可表示的。
这是一篇解释双精度64位二进制格式如何工作的文章。让我们看看如何使用这种二进制格式来保存(表示){{9,007,199,254,740,992}}。
使用一个简化的版本来演示从{{4,503,599,627,370,496}}开始的过程:
  1 . 0000 ---- 0000  *  2^52            =>  1  0000 ---- 0000.  
     |-- 52 bits --|    |exponent part|        |-- 52 bits --|

在箭头左侧,我们有比特值1和相邻的基数点。通过使用左侧的指数部分,基数点向右移动52步。基数点最终在末尾,我们得到了4503599627370496的纯二进制数。
现在让我们将小数部分逐步增加1,直到所有比特都设置为1,这等于十进制中的9,007,199,254,740,991
  1 . 0000 ---- 0000  *  2^52  =>  1  0000 ---- 0000.  
                       (+1)
  1 . 0000 ---- 0001  *  2^52  =>  1  0000 ---- 0001.  
                       (+1)
  1 . 0000 ---- 0010  *  2^52  =>  1  0000 ---- 0010.  
                       (+1)
                        . 
                        .
                        .
  1 . 1111 ---- 1111  *  2^52  =>  1  1111 ---- 1111. 

由于64位双精度格式严格分配了52位用于小数部分,如果我们再添加1就没有更多的位可用了,因此我们需要将所有位设置回0,并操作指数部分:

  ┏━━▶ This bit is implicit and persistent.
  ┃        
  1 . 1111 ---- 1111  *  2^52      =>  1  1111 ---- 1111. 
     |-- 52 bits --|                     |-- 52 bits --|

                          (+1)

  1 . 0000 ---- 0000  *  2^52 * 2  =>  1  0000 ---- 0000. * 2  
     |-- 52 bits --|                     |-- 52 bits --|
                                      (By consuming the 2^52, radix
                                       point has no way to go, but
                                       there is still one 2 left in
                                       exponent part)
  =>  1 . 0000 ---- 0000  *  2^53 
         |-- 52 bits --| 

现在我们得到了{{9,007,199,254,740,992}},对于大于该数的数字,该格式只能处理2的增量,因为小数部分每增加1就会被指数部分左边的2乘以。这就是为什么当数字大于{{9,007,199,254,740,992}}时,双精度64位二进制格式无法保存奇数的原因:
                            (consume 2^52 to move radix point to the end)
  1 . 0000 ---- 0001  *  2^53  =>  1  0000 ---- 0001.  *  2
     |-- 52 bits --|                 |-- 52 bits --|

按照这个模式,当数字大于9,007,199,254,740,992 * 2 = 18,014,398,509,481,984时,只能保留四分之一的小数:

input: 18014398509481984 + 1  output: 18014398509481984  // expected: 18014398509481985
input: 18014398509481984 + 2  output: 18014398509481984  // expected: 18014398509481986
input: 18014398509481984 + 3  output: 18014398509481984  // expected: 18014398509481987
input: 18014398509481984 + 4  output: 18014398509481988  // expected: 18014398509481988

那么在 [ 2 251 799 813 685 248, 4 503 599 627 370 496 ) 范围内的数字呢?

 1 . 0000 ---- 0001  *  2^51  =>  1 0000 ---- 000.1
     |-- 52 bits --|                |-- 52 bits  --|

在二进制中,数值0.1恰好等于2^-1(=1/2)(=0.5)。因此,当数字小于4,503,599,627,370,496(2的52次方)时,有一个比特位可用于表示整数的一半。{{}}
input: 4503599627370495.5   output: 4503599627370495.5  
input: 4503599627370495.75  output: 4503599627370495.5  
            

小于2,251,799,813,685,248(2的51次方)

input: 2251799813685246.75   output: 2251799813685246.8  // expected: 2251799813685246.75 
input: 2251799813685246.25   output: 2251799813685246.2  // expected: 2251799813685246.25 
input: 2251799813685246.5    output: 2251799813685246.5
/**
   Please note that if you try this yourself and, say, log 
   these numbers to the console, they will get rounded. JavaScript
   rounds if the number of digits exceed 17. The value 
   is internally held correctly:
*/
            
input: 2251799813685246.25.toString(2) 
output: "111111111111111111111111111111111111111111111111110.01"
input: 2251799813685246.75.toString(2) 
output: "111111111111111111111111111111111111111111111111110.11"
input: 2251799813685246.78.toString(2)   
output: "111111111111111111111111111111111111111111111111110.11"
指数部分的有效范围是多少?格式为其分配了11位。

来自维基百科(如需更多详细信息,请转到该网站)

IEEE 754 Double Floating Point Format.svg

所以为了使指数部分为2^52,我们需要将e设置为1075。


41

Jimmy的回答正确地表示了JavaScript整数连续谱,范围为-90071992547409929007199254740992(抱歉,9007199254740993,你可能认为自己是9007199254740993,但你错了!下面或在jsfiddle中演示)。

console.log(9007199254740993);

然而,没有一个程序能够找到/证明这一点(除了CoolAJ86在他的答案中提到的需要28.56年才能完成的方法 ;),因此这里有一种稍微更有效的方法来实现这个目标(准确地说,它比原来的方法快了约28.559999999968312年 :),同时还附带了一个测试代码片段

/**
 * Checks if adding/subtracting one to/from a number yields the correct result.
 *
 * @param number The number to test
 * @return true if you can add/subtract 1, false otherwise.
 */
var canAddSubtractOneFromNumber = function(number) {
    var numMinusOne = number - 1;
    var numPlusOne = number + 1;
    
    return ((number - numMinusOne) === 1) && ((number - numPlusOne) === -1);
}

//Find the highest number
var highestNumber = 3; //Start with an integer 1 or higher

//Get a number higher than the valid integer range
while (canAddSubtractOneFromNumber(highestNumber)) {
    highestNumber *= 2;
}

//Find the lowest number you can't add/subtract 1 from
var numToSubtract = highestNumber / 4;
while (numToSubtract >= 1) {
    while (!canAddSubtractOneFromNumber(highestNumber - numToSubtract)) {
        highestNumber = highestNumber - numToSubtract;
    }
    
    numToSubtract /= 2;
}        

//And there was much rejoicing.  Yay.    
console.log('HighestNumber = ' + highestNumber);


8
@CoolAJ86:哈哈,我期待着2040年3月15日。如果我们的号码匹配,我们应该开个派对 :) - Briguy37
var x=Math.pow(2,53)-3;while (x!=x+1) x++; -> 9007199254740991 - MickLH
@MickLH:使用此代码我得到了9007199254740992。你在使用哪个JavaScript引擎进行测试? - Briguy37
你用自己的代码得到了9007199254740992,我出于偏执的原因没有使用x的最终值,而是使用了x++的最终计算结果。顺便提一下,这是在Google Chrome中运行的。 - MickLH
谢谢,但我确实知道后增量的语义,并使用它的“最终评估”(而不是x的最终值),因为它提供了一个数字,即(x!= x + 1)也就是“+1有效的最后一个数字”,而不是“第一个失败的数字”。 - MickLH
显示剩余6条评论

34

为了安全起见

var MAX_INT = 4294967295;

推理

我以为我会聪明一些,用更实用的方法找到使得 x + 1 === x 的值。

我的机器每秒只能计算1000万次左右……所以我将在28.56年后发布最终答案。

如果你等不了那么久,我敢打赌:

  • 大多数循环不会运行28.56年
  • 9007199254740992 === Math.pow(2, 53) + 1 就已经足够证明了
  • 你应该使用4294967295,它是 Math.pow(2,32) - 1,以避免位移时出现预期外的问题

查找 x + 1 === x

(function () {
  "use strict";

  var x = 0
    , start = new Date().valueOf()
    ;

  while (x + 1 != x) {
    if (!(x % 10000000)) {
      console.log(x);
    }

    x += 1
  }

  console.log(x, new Date().valueOf() - start);
}());

5
你可以从2的53次方减去2开始测试吗?(是的,你可以,我刚试过了,即使加上-3以确保安全:var x=Math.pow(2,53)-3;while (x!=x+1) x++;)-> 9007199254740991 - MickLH
1
很好的答案!此外,我知道这个值已经确定了,但为什么不使用二分查找来寻找它呢? - higuaro
1
有什么好玩的呢?此外,@Briguy37已经超过我了:https://dev59.com/hnVC5IYBdhLWcg3wZwNT#11639621 - coolaj86
请注意,基于32位的“安全”MAX_INT在与日期值进行比较时将无法工作。4294967295已经过时了! - Jerry
1
答案“为了安全起见:var MAX_INT = 4294967295;”并不好笑。如果你没有进行位移操作,就不用担心它(除非你需要一个大于4294967295的整数,在这种情况下,你应该将其存储为字符串并使用bigint库)。 - coolaj86
显示剩余3条评论

31

简短的回答是“这要看情况”。

如果你在使用位运算符(或者是在引用数组的长度),范围如下:

无符号数:0…(-1>>>0)

有符号数:(-(-1>>>1)-1)…(-1>>>1)

(正好位运算符和数组的最大长度都限制在32位整数内。)

如果你没有使用位运算符或者处理数组长度:

有符号数:(-Math.pow(2,53))…(+Math.pow(2,53))

这些限制是由“Number”类型的内部表示所强加的,通常对应于IEEE 754双精度浮点表示。(请注意,与典型的有符号整数不同的是,负极限的大小与正极限的大小相同,这是由内部表示的特性决定的,它实际上包括一个负零!)


这就是我想要的答案,关于如何将X转换为32位整数或无符号整数。因此,我给你的回答点了赞。 - Charlie Affumigato

29

ECMAScript 6:

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

1
请注意,这并不是所有浏览器都支持的功能!今天iOS(甚至包括chrome)、Safari和IE都无法使用它。 - cregox
6
请仔细阅读答案,我们不使用ECMAScript 6中Number.MAX_SAFE_INTEGER的默认实现,而是通过Math.pow(2, 53)-1来定义它。 - WaiKit Kung
我原本以为这只是一个对ECMA 6实现方式的参考!:P 我认为我的评论仍然是有价值的,只是需要看上下文。;) - cregox
3
通过逆推的方式计算 MAX_SAFE_INTEGER 在所有浏览器中是否可靠?应该前向计算还是逆推计算?即,Number.MAX_SAFE_INTEGER = 2 * (Math.pow(2, 52) - 1) + 1;可以翻译为:在所有浏览器中,通过逆向计算来确定 MAX_SAFE_INTEGER 是否可靠?是否应该使用正向计算方法?例如:Number.MAX_SAFE_INTEGER = 2 * (Math.pow(2, 52) - 1) + 1; - kjv
Math.pow(2, 53)-1 这个操作安全吗?它会比最大的安全整数还要大一。 - ioquatix
是的,这个计算是正确的。Math.pow(2, 53)可以准确表示,并且减法运算也是明确定义和精确的。 - Daira Hopwood

13

其他人可能已经给出了通用的答案,但我认为提供一种快速确定它的方法是一个好主意:

for (var x = 2; x + 1 !== x; x *= 2);
console.log(x);

在 Chrome 30 中,这个方法能够在不到一毫秒的时间内得出 9007199254740992。

该方法会测试 2 的幂次方,找到其中一个数,使得在加上 1 后等于自身。


它可能会导致你的应用程序崩溃。 - Sapphire_Brick

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