數學 - JavaScript 中數字可以達到而不失去精度的最大整數值是多少?
這是由語言定義的嗎?有定義的最大值嗎?在不同的瀏覽器中會有所不同嗎?
答案#1
JavaScript 有兩種數字類型:Number 和 BigInt。
最常使用的數字類型 Number 是一個 64 位元浮點數 IEEE 754 數字。
此類型最大的精確整數值是 Number.MAX_SAFE_INTEGER,其值為:
- 253-1,或
- +/- 9,007,199,254,740,991,或
- 九千九百七十兆一千九百九十九億二千五百四十七萬四千九百九十一
為了讓這更有概念:一千兆字節是一拍字節(或一千太字節)。
此處的「安全」指的是能夠精確表示整數並正確比較它們的能力。
摘自規格:
請注意,所有其絕對值不大於 253 的正整數和負整數都可以在
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。
答案#2
>= 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答案#3
253 == 9 007 199 254 740 992。這是因為 Number 是以 52 位元尾數的浮點數形式儲存的。
最小值是 -253。
這讓一些有趣的事情發生
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
答案#4
在 JavaScript 中,有一個數字叫做 Infinity。
範例:
(Infinity>100)
=> true
// Also worth noting
Infinity - 1 == Infinity
=> true
Math.pow(2,1024) === Infinity
=> true
對於關於這個主題的某些問題,這可能已經足夠。
答案#5
許多早期的答案已經顯示 9007199254740992 === 9007199254740992 + 1 是 true,以驗證 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。這就是為什麼 雙精度 64 位元二進位格式 在數字大於 9,007,199,254,740,992 時無法表示奇數的原因:
(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 時,分數只能保持 4 次:
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 位元。
來自維基百科(更多詳情,請前往該網站)
所以要使指數部分為 2^52,我們正好需要設 e=1075。
答案#6
Jimmy 的回答正確地表示了連續的 JavaScript 整數範圍,從 -9007199254740992 到 9007199254740992(包含兩端)(抱歉 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);答案#7
為了安全起見
var MAX_INT = 4294967295;
推理
我以為我很聰明,想用更務實的方法找出滿足 x + 1 === x 的數值。
我的機器每秒只能計算大約一千萬次……所以我將在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);
}());
答案#8
簡短的回答是「這取決於情況。」
如果你在任何地方使用位元運算子(或如果你在引用陣列的長度),範圍如下:
無符號:0…(-1>>>0)
簽名:(-(-1>>>1)-1)…(-1>>>1)
(碰巧,位元運算子和陣列的最大長度都限制為 32 位元整數。)
如果你沒有使用位元運算子或處理陣列長度:
簽名:(-Math.pow(2,53))…(+Math.pow(2,53))
這些限制是由「Number」類型的內部表示所施加的,該表示通常對應於 IEEE 754 雙精度浮點數表示。(注意,與典型的有符號整數不同,負限制的大小與正限制的大小相同,這是由於內部表示的特性,內部表示實際上包括一個負 0!)
答案#9
ECMAScript 6:
Number.MAX_SAFE_INTEGER = Math.pow(2, 53)-1;
Number.MIN_SAFE_INTEGER = -Number.MAX_SAFE_INTEGER;
答案#10
其他人可能已經給出了通用答案,但我覺得提供一個快速判斷的方法會是個好主意:
for (var x = 2; x + 1 !== x; x *= 2);
console.log(x);
在 Chrome 30 中,這在不到一毫秒的時間內給我 9007199254740992。
它將測試 2 的冪次,以找出哪一個當「加上」1時等於它本身。
