1. 程式人生 > 實用技巧 >ECMAScript6-基礎6-數值儲存與資料擴充套件

ECMAScript6-基礎6-數值儲存與資料擴充套件

JS與IEEE754標準

概念

由於不同機器所選用的基數、尾數位長度和階碼位長度不同,因此對浮點數的表示有較大差別,這不利於軟體在不同計算機之間的移植。為此,美國IEEE(電器及電子工程師協會)提出了一個從系統角度支援浮點數的表示方法,稱為IEEE754標準(IEEE,1985),當今流行的計算機幾乎都採用了這一標準;

Bug

Java中浮點數,既float和double,都是採用的IEEE754標準。無論在java、python、javaScript裡面都存在 1 + 2 != 3 問題,這個問題的產生根源在於計算儲存數字是二進位制,對無限迴圈小數和無理數採用雙精度64位double浮點數_float為32位,即52位小數+11位指數+1位符號。超過52位小數溢位而產生精度丟失。

這個問題並不只是在Javascript中才會出現,任何使用二進位制浮點數的程式語言都會有這個問題,只不過在 C++/C#/Java 這些語言中已經封裝好了方法來避免精度的問題,而 JavaScript 是一門弱型別的語言,從設計思想上就沒有對浮點數有個嚴格的資料型別,所以精度誤差的問題就顯得格外突出。

造成的原因

JS中的數字型別只有Number一種,Number型別採用IEEE754標準中的“雙精度浮點數”來表示一個數字,不區分整數和浮點數,即所有數字都採用雙精度浮點數方式來儲存;

造成的原因:十進位制整數轉二進位制

十進位制整數轉二進位制,是沒任何問題的,就以數字6為例:

6/2=3...0

3/2=1...1

1/2=0...1

所以,6的二進位制就是110,總之一句話:十進位制整數總是能正確的用二進位制來表達;

造成的原因:十進位制浮點數轉二進位制

相對於十進位制整數總能正確轉為二進位制,十進位制小數卻有時不能正確轉為二進位制;

以0.8125為例來進行說明:

0.8125*2=1.625取整是1

0.625*2=1.25 取整是1

0.25*2=0.5 取整是0

0.5*2=1.0 取整是1

即0.8125的二進位制是0.1101(第一次所得到為最高位,最後一次得到為最低位)

那麼,看一下0.1對應的二進位制表示:

0.1*2=0.2======取出整數部分0

0.2*2=0.4======取出整數部分0

0.4*2=0.8======取出整數部分0

0.8*2=1.6======取出整數部分1

0.6*2=1.2======取出整數部分1

0.2*2=0.4======取出整數部分0

0.4*2=0.8======取出整數部分0

0.8*2=1.6======取出整數部分1

0.6*2=1.2======取出整數部分1

0.2*2=0.4======取出整數部分0

0.4*2=0.8======取出整數部分0

0.8*2=1.6======取出整數部分1

0.6*2=1.2======取出整數部分1

這裡,加粗字型會不斷重複,直到超過“雙精度浮點數”長度,沒辦法,就只能進行擷取,可問題在於,一旦截短,那肯定比原先的0.1要大;

科學計數法

科學記數法是一種把一個數表示成a與10的n次冪相乘的形式(1≤a<10,n為整數)的記數法。;

例如:19971400000000=1.99714×10^13。計算器或電腦表達10的冪是一般是用E或e,也就是1.99714E13=19971400000000;

科學記數法的形式是由兩個數的乘積組成的。表示為a×10^b(aEb),其中一個因數為a(1≤|a|<10),另一個因數為10^n。

科學計數法的精確度

運用科學記數法a×10^n的數字,它的精確度以a的最後一個數在原數中的數位為準;

13600,精確到十位,記作:1.360X10^4

13600 ,精確到百位,記作:1.36X10^4

13600,精確到千位,記作:1.3X10^4

十進位制的5.0,寫成二進位制是101.0,相當於1.01×2^2

十進位制的-5.0,寫成二進位制是-101.0,相當於-1.01×2^2,推薦閱讀《浮點數的二進位制表示》

在二進位制裡面,即a×2^b,1≤a<2,也就是說,a可以寫成1.xxxxxx的形式,其中xxxxxx表示小數部分。IEEE 754規定,在計算機內部儲存a時,預設這個數的第一位總是1,因此可以被捨去,只儲存後面的xxxxxx部分。比如儲存1.01的時候,只儲存01,等到讀取的時候,再把第一位的1加上去。這樣做的目的,是節省1位有效數字。以64位浮點數為例,留給a只有52位,將第一位的1捨去以後,等於可以儲存53位有效數字

所以js浮點數表示的數字為[-1*2^53*2^1023,1*2^53*2^1024],

±1為符號位

2^53為小數位(53-1)

2^1024中1024位指數位(1024*2=2048=2^11為指數位)

浮點數表示法儲存結構

在 IEEE754 中,雙精度浮點數採用 64 位儲存,即 8 個位元組表示一個浮點數 。其儲存結構如下圖所示:

1位符號位、11位指數位、52位小數位

這裡的11位指數位,取值範圍是2^11 = 2048,那麼在科學表示式a×2^b中,b的取值範圍就是0-2048;

指數位

雙精度浮點數中有11位作為指數為,即11個二進位制單位,指數位把全0和全1的情況,單獨拿出來幹別的了,也就是2^11 = 2048,先把特殊數字除去,那就剩下2047個數字;

指數位:00000000

位長:11

表示範圍:0<=e<=(2^11)-1,即等於等於0,小於等於2047

為了表示負指數,以中間值進行偏移:bias = 1023

偏移結果:-1023<=e-bias<=1024

為了表示正負指數,拿出一位來當做符號位,那就剩下(11-1=10)來作為數值,原先是2^11來作為取值取值範圍,且都為正數;當拿出1位作為符號位之後,取值範圍變成:

從0000-0000-000到1111-1111-111

變成

0111-1111-111到0000-0000-000,即-1023到-0

1000-0000-000到1111-1111-111,即+0到1023

那麼,我們可以推匯出雙精度浮點數的計算公式:

Value = -1^s * 2^E * (1.f)

E = e - bias

這裡的s就是64位的第一位符號位

這裡的E就是64位中的11位指數位

這裡的f就是52位數字精確度位

e原先的取值範圍是:0<=e<=(2^11)-1=2047

為了分別出正指數與負指數,用了一位符號位,最小的負數是0111-1111-111,即-1023,即bias=1023,那麼,引入偏移之後的e取值範圍是:-1023<=e-bias<=1024

當e-bias=-1023,表示正負0,或者,非規格浮點數

當e-bias=1024,表示正負無窮,或者NaN

總結如下:在科學表示式a×2^b中,

①b能獲取的最大值是2047(2^11-1)

②取中間值進行偏移,用來表示負指數來表示小數,也就是說b的取值範圍是 [-1023,1024]

即,11位的指數位能夠表示的數值範圍為 2^1024到2^-1023,超出這個範圍的數無法表示。2^1024和2^-1023轉換為科學計數法如下所示:

2^1024= 1.7976931348623157* 10^308

2^-1023= 5* 10^-324

因此,JS中能表示的最大值Number.MAX_VALUE=1.7976931348623157*10^308,最小值Number.MIN_VALUE=5*10^-324,JAVA雙精度型別double也是如此。

如果數字超過最大值或最小值,JS將返回一個不正確的值,這稱為“正向溢位(overflow)”/“負向溢位(underflow)”;

雙精度浮點數總結

在 64 位的二進位制中:

1位符號位決定了一個數的正負

11位指數位決定了數值的大小

52位小數位決定了數值的精度

IEEE754規定,小數位第一位預設總是1,因此,在表示精度的位數前面,還存在一個“隱藏位”固定為1 ,但它不儲存在 64位浮點數之中。也就是說,有效數字總是1.xx...xx的形式,其中xx..xx 的部分儲存在64位浮點數之中,最長為52位。所以,JS提供的有效數字最長為53個二進位制位,其內部實際的表現形式為:

(-1)^符號位 * 1.xx...xx * 2^指數位

這意味著,JS能表示並進行精確算術運算的整數範圍為:[-2^53-1,2^53-1],即從最小值Number.MIN_SAFE_INTEGER=-9007199254740991到最大值Number.MAX_SAFE_INTEGER=9007199254740991之間的範圍;

數值的擴充套件

概念

從ES5開始,在嚴格模式之中,八進位制就不再允許使用字首0表示,到了ES6 進一步明確,要使用字首0o表示;(第一個是數字0,第二個是字母o)

ES6 提供了二進位制和八進位制數值的新的寫法:分別用字首0b(或0B)和0o(或0O)表示;

案例講解/注意事項

0b111110111 === 503 // true

0o767 === 503 // true

// 非嚴格模式

(function(){

console.log(0o11 ===011);

})() // true

// 嚴格模式

(function(){

'use strict';

console.log(0o11 === 011);

})() // Uncaught SyntaxError: Octal literals are not allowed in strict mode.

如果要將0b和0o字首的字串數值轉為十進位制,要使用Number方法:

Number('0b111') // 7

Number('0o10') // 8

Number.isFinite()/Number.isNaN()

概念

ES6為Number物件新增Number.isFinite()、Number.isNaN()方法;

Number.isFinite():用來檢查一個數值是否為有限的(finite),即不是Infinity;如果引數型別不是數值,Number.isFinite一律返回false;

Number.isNaN():用來檢查一個值是否為NaN;如果引數型別不是NaN,Number.isNaN一律返回false;

案例講解/注意事項

Number.isFinite(15); // true

Number.isFinite(0.8); // true

Number.isFinite(NaN); // false

Number.isFinite(Infinity); // false

Number.isFinite(-Infinity); // false

Number.isFinite('foo'); // false

Number.isFinite('15'); // false

Number.isFinite(true); // false

Number.isNaN(NaN) // true

Number.isNaN(15) // false

Number.isNaN('15') // false

Number.isNaN(true) // false

Number.isNaN(9/NaN) // true

Number.isNaN('true' / 0) // true

Number.isNaN('true' / 'true') // true

與之前全域性方法isFinite()和isNaN()的區別在於:傳統方法先呼叫Number()將非數值的值轉為數值,然後再進行判斷;

而這兩個新增方法只對數值有效,Number.isFinite()對於非數值一律返回false, Number.isNaN()只有對於NaN才返回true,非NaN一律返回false:

isFinite(25) // true

isFinite("25") // true

Number.isFinite(25) // true

Number.isFinite("25") // false

isNaN(NaN) // true

isNaN("NaN") // true

Number.isNaN(NaN) // true

Number.isNaN("NaN") // false

Number.isNaN(1) // false

即:傳統全域性方法isFinite()和isNaN()會利用Number()對引數先做一次轉換,再去判斷,而新增的Number.isFinite()和Number.isNaN()則直接對引數進行判斷;

Number.parseInt()/Number.parseFloat()

概念

ES6將全域性方法parseInt()、parseFloat()移植到Number物件上面,行為完全保持不變;這樣做的目的是逐步減少全域性性方法,使得語言逐步模組化;

案例講解/注意事項

// ES5的寫法

parseInt('12.34') // 12

parseFloat('123.45#') // 123.45

// ES6的寫法

Number.parseInt('12.34') // 12

Number.parseFloat('123.45#') // 123.45

Number.parseInt === parseInt // true

Number.parseFloat === parseFloat // true

Number.isInteger()

概念

Number.isInteger()用來判斷一個數值是否為整數;由於JS內部中整數和浮點數採用的是同樣的儲存方法,所以25和25.0被視為同一個值;如果引數不是數值,Number.isInteger返回false;

案例講解/注意事項

Number.isInteger(25) // true

Number.isInteger(25.1) // false

Number.isInteger(25) // true

Number.isInteger(25.0) // true

Number.isInteger() // false

Number.isInteger(null) // false

Number.isInteger('15') // false

Number.isInteger(true) // false

由於JS採用IEEE 754 標準,數值儲存為64位雙精度格式,數值精度最多可以達到 53 個二進位制位(1個隱藏位與 52個有效位)。如果數值的精度超過這個限度,第54位及後面的位就會被丟棄,這種情況下,Number.isInteger可能會誤判,例如:

Number.isInteger(3.0000000000000002) // true

Number.isInteger的引數明明不是整數,但是會返回true。原因就是這個小數的精度達到了小數點後16個十進位制位,轉成二進位制位超過了53個二進位制位,導致最後的那個2被丟棄了;

類似的情況還有,如果一個數值的絕對值小於Number.MIN_VALUE(5E-324),即小於 JS能夠分辨的最小值,會被自動轉為 0。這時,Number.isInteger也會誤判:

Number.isInteger(5E-324) // false

Number.isInteger(5E-325) // true

總之,如果對資料精度的要求較高,不建議使用Number.isInteger()判斷一個數值是否為整數;

Number.EPSILON

概念

ES6在Number物件上面,新增一個極小的常量Number.EPSILON,根據規格它表示1與大於1的最小浮點數之間的差值;

對於64位浮點數來說,大於1的最小浮點數相當於二進位制的1.00...001,小數點後面有連續51個零。這個值減去1之後,就等於2的-52 次方;

Number.EPSILON實際上是JS能夠表示的最小精度,誤差如果小於這個值,就可以認為已經沒有意義了,即不存在誤差了;

案例講解/注意事項

Number.EPSILON === Math.pow(2, -52)

// true 等於2的-52 次方

Number.EPSILON

// 2.220446049250313e-16

Number.EPSILON.toFixed(20)

// "0.00000000000000022204"

為什麼新增這項技術/應用場景

引入一個這麼小的量的目的,在於為浮點數計算時,設定一個誤差範圍;

我們知道浮點數計算是不精確的,

Number.EPSILON可以用來設定“能夠接受的誤差範圍”。比如,誤差範圍設為 2 的-50 次方(即Number.EPSILON * Math.pow(2, 2)),即如果兩個浮點數的差小於這個值,我們就認為這兩個浮點數相等。

因此,Number.EPSILON的實質是一個可以接受的最小誤差範圍。

0.1 + 0.2

// 0.30000000000000004

0.1 + 0.2 - 0.3

// 5.551115123125783e-17

5.551115123125783e-17.toFixed(20)

// '0.00000000000000005551'

0.1 + 0.2 === 0.3 // false

5.551115123125783e-17 < Number.EPSILON * Math.pow(2, 2)

// true

function withinErrorMargin (left, right) {

return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);

}

0.1 + 0.2 === 0.3 // false

withinErrorMargin(0.1 + 0.2, 0.3) // true

1.1 + 1.3 === 2.4 // false

withinErrorMargin(1.1 + 1.3, 2.4) // true

安全整數和 Number.isSafeInteger()

概念

JS能夠準確表示的整數範圍在-2^53到2^53之間(不含兩個端點),超過這個範圍,無法精確表示這個值;

ES6 引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER這兩個常量,用來表示這個範圍的上下限;

Number.isSafeInteger()則是用來判斷一個整數是否落在這個範圍之內;這個函式的實現很簡單,就是跟安全整數的兩個邊界值比較一下,實際使用這個函式時,需要注意。驗證運算結果是否落在安全整數的範圍內,不要只驗證運算結果,而要同時驗證參與運算的每個值;

舉個例子:

9007199254740993 - 990 = 9007199254740003

但由於JS只能識別到90071992547409932,所以,最後這個減法運算結果是9007199254740002,且Number.isSafeInteger(9007199254740993 - 990)結果為true;

所以,即便我們看到Number.isSafeInteger為true,不代表表示式是沒問題的,還需要分別驗證9007199254740993 與 990 的合法性;

所以,如果只驗證運算結果是否為安全整數,很可能得到錯誤結果,還需要同時驗證運算數和運算結果;

案例講解/注意事項

JS能夠準確表示的整數範圍在-2^53到2^53之間(不含兩個端點),超過這個範圍,無法精確表示這個值:

Math.pow(2, 53) // 9007199254740992

9007199254740992 // 9007199254740992

9007199254740993 // 9007199254740992

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

// true

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

// true

Number.MAX_SAFE_INTEGER === 9007199254740991

// true

Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER

// true

Number.MIN_SAFE_INTEGER === -9007199254740991

// true

Number.isSafeInteger('a') // false

Number.isSafeInteger(null) // false

Number.isSafeInteger(NaN) // false

Number.isSafeInteger(Infinity) // false

Number.isSafeInteger(-Infinity) // false

Number.isSafeInteger(3) // true

Number.isSafeInteger(1.2) // false

Number.isSafeInteger(9007199254740990) // true

Number.isSafeInteger(9007199254740992) // false

Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false

Number.isSafeInteger(Number.MIN_SAFE_INTEGER) // true

Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true

Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false

Number.isSafeInteger(9007199254740993)

// false

Number.isSafeInteger(990)

// true

Number.isSafeInteger(9007199254740993 - 990)

// true

9007199254740993 - 990

// 返回結果 9007199254740002

// 正確答案應該是 9007199254740003

上面程式碼中,9007199254740993不是一個安全整數,但是Number.isSafeInteger會返回結果,顯示計算結果是安全的。這是因為,這個數超出了精度範圍,導致在計算機內部,以9007199254740992的形式儲存。

9007199254740993 === 9007199254740992

// true

所以,如果只驗證運算結果是否為安全整數,很可能得到錯誤結果。下面的函式可以同時驗證兩個運算數和運算結果:

function trusty (left, right, result) {

if (

Number.isSafeInteger(left) &&

Number.isSafeInteger(right) &&

Number.isSafeInteger(result)

) {

return result;

}

throw new RangeError('Operation cannot be trusted!');

}

trusty(9007199254740993, 990, 9007199254740993 - 990)

// RangeError: Operation cannot be trusted!

trusty(1, 2, 3)

// 3

Math物件的擴充套件

概念

ES6在Math物件上新增了17個與數學相關的方法,所有這些方法都是靜態方法,只能在Math物件上呼叫;

Math.trunc()

Math.trunc方法用於去除一個數的小數部分,返回整數部分。

Math.trunc(4.1) // 4

Math.trunc(4.9) // 4

Math.trunc(-4.1) // -4

Math.trunc(-4.9) // -4

Math.trunc(-0.1234) // -0

對於非數值,Math.trunc內部使用Number方法將其先轉為數值

Math.trunc('123.456') // 123

Math.trunc(true) //1

Math.trunc(false) // 0

Math.trunc(null) // 0

對於空值和無法擷取整數的值,返回NaN。

Math.trunc(NaN); // NaN

Math.trunc('foo'); // NaN

Math.trunc(); // NaN

Math.trunc(undefined) // NaN

Math.sign()

Math.sign方法用來判斷一個數到底是正數、負數、還是零。對於非數值,會先將其轉換為數值。它會返回五種值。

①引數為正數,返回+1;

②引數為負數,返回-1;

③引數為 0,返回0;

④引數為-0,返回-0;

⑤其他值,返回NaN。

Math.sign(-5) // -1

Math.sign(5) // +1

Math.sign(0) // +0

Math.sign(-0) // -0

Math.sign(NaN) // NaN

如果引數是非數值,會自動轉為數值。對於那些無法轉為數值的值,會返回NaN

Math.sign('') // 0

Math.sign(true) // +1

Math.sign(false) // 0

Math.sign(null) // 0

Math.sign('9') // +1

Math.sign('foo') // NaN

Math.sign() // NaN

Math.sign(undefined) // NaN

Math.cbrt()

Math.cbrt()方法用於計算一個數的立方根。

Math.cbrt(-1) // -1

Math.cbrt(0) // 0

Math.cbrt(1) // 1

Math.cbrt(2) // 1.2599210498948732

對於非數值,Math.cbrt()方法內部也是先使用Number()方法將其轉為數值

Math.cbrt('8') // 2

Math.cbrt('hello') // NaN

Math.clz32()

Math.clz32()方法將引數轉為 32 位無符號整數的形式,然後返回這個 32 位值裡面有多少個前導 0

Math.clz32(0) // 32

Math.clz32(1) // 31

Math.clz32(1000) // 22

Math.clz32(0b01000000000000000000000000000000) // 1

Math.clz32(0b00100000000000000000000000000000) // 2

上面程式碼中,0 的二進位制形式全為 0,所以有 32 個前導 0;1 的二進位制形式是0b1,只佔 1 位,所以 32 位之中有 31 個前導 0;1000 的二進位制形式是0b1111101000,一共有 10 位,所以 32 位之中有 22 個前導 0。

左移運算子(<<)與Math.clz32方法直接相關

Math.clz32(0) // 32

Math.clz32(1) // 31

Math.clz32(1 << 1) // 30

Math.clz32(1 << 2) // 29

Math.clz32(1 << 29) // 2

對於小數,Math.clz32方法只考慮整數部分

Math.clz32(3.2) // 30

Math.clz32(3.9) // 30

對於空值或其他型別的值,Math.clz32方法會將它們先轉為數值,然後再計算

Math.clz32() // 32

Math.clz32(NaN) // 32

Math.clz32(Infinity) // 32

Math.clz32(null) // 32

Math.clz32('foo') // 32

Math.clz32([]) // 32

Math.clz32({}) // 32

Math.clz32(true) // 31

Math.imul()

Math.imul方法返回兩個數以 32 位帶符號整數形式相乘的結果,返回的也是一個 32 位的帶符號整數。

Math.imul(2, 4) // 8

Math.imul(-1, 8) // -8

Math.imul(-2, -2) // 4

如果只考慮最後 32 位,大多數情況下,Math.imul(a, b)與a * b的結果是相同的,即該方法等同於(a * b)|0的效果(超過 32 位的部分溢位)。之所以需要部署這個方法,是因為 JS有精度限制,超過 2 的 53 次方的值無法精確表示。這就是說,對於那些很大的數的乘法,低位數值往往都是不精確的,Math.imul方法可以返回正確的低位數值。

Math.fround()

Math.fround方法返回一個數的32位單精度浮點數形式。

對於32位單精度格式來說,數值精度是24個二進位制位(1 位隱藏位與 23 位有效位),所以對於 -224 至 224 之間的整數(不含兩個端點),返回結果與引數本身一致。

Math.fround(0) // 0

Math.fround(1) // 1

Math.fround(2 ** 24 - 1) // 16777215

如果引數的絕對值大於 224,返回的結果便開始丟失精度。

Math.fround(2 ** 24) // 16777216

Math.fround(2 ** 24 + 1) // 16777216

Math.fround方法的主要作用,是將64位雙精度浮點數轉為32位單精度浮點數。如果小數的精度超過24個二進位制位,返回值就會不同於原值,否則返回值不變(即與64位雙精度值一致)。

// 未丟失有效精度

Math.fround(1.125) // 1.125

Math.fround(7.25) // 7.25

// 丟失精度

Math.fround(0.3) // 0.30000001192092896

Math.fround(0.7) // 0.699999988079071

Math.fround(1.0000000123) // 1

對於 NaN 和 Infinity,此方法返回原值。對於其它型別的非數值,Math.fround 方法會先將其轉為數值,再返回單精度浮點數。

Math.fround(NaN) // NaN

Math.fround(Infinity) // Infinity

Math.fround('5') // 5

Math.fround(true) // 1

Math.fround(null) // 0

Math.fround([]) // 0

Math.fround({}) // NaN

Math.hypot()

Math.hypot方法返回所有引數的平方和的平方根

Math.hypot(3, 4); // 5

Math.hypot(3, 4, 5); // 7.0710678118654755

Math.hypot(); // 0

Math.hypot(NaN); // NaN

Math.hypot(3, 4, 'foo'); // NaN

Math.hypot(3, 4, '5'); // 7.0710678118654755

Math.hypot(-3); // 3

如果引數不是數值,Math.hypot方法會將其轉為數值。只要有一個引數無法轉為數值,就會返回 NaN。

Math.expm1()

Math.expm1(x)返回 ex - 1,即Math.exp(x) - 1。

Math.expm1(-1) // -0.6321205588285577

Math.expm1(0) // 0

Math.expm1(1) // 1.718281828459045

Math.log1p()

Math.log1p(x)方法返回1 + x的自然對數,即Math.log(1 + x)。如果x小於-1,返回NaN。

Math.log1p(1) // 0.6931471805599453

Math.log1p(0) // 0

Math.log1p(-1) // -Infinity

Math.log1p(-2) // NaN

Math.log10()

Math.log10(x)返回以 10 為底的x的對數。如果x小於 0,則返回 NaN。

Math.log10(2) // 0.3010299956639812

Math.log10(1) // 0

Math.log10(0) // -Infinity

Math.log10(-2) // NaN

Math.log10(100000) // 5

Math.log2()

Math.log2(x)返回以 2 為底的x的對數。如果x小於 0,則返回 NaN。

Math.log2(3) // 1.584962500721156

Math.log2(2) // 1

Math.log2(1) // 0

Math.log2(0) // -Infinity

Math.log2(-2) // NaN

Math.log2(1024) // 10

Math.log2(1 << 29) // 29

雙曲函式方法

Math.sinh(x) 返回x的雙曲正弦(hyperbolic sine)

Math.cosh(x) 返回x的雙曲餘弦(hyperbolic cosine)

Math.tanh(x) 返回x的雙曲正切(hyperbolic tangent)

Math.asinh(x) 返回x的反雙曲正弦(inverse hyperbolic sine)

Math.acosh(x) 返回x的反雙曲餘弦(inverse hyperbolic cosine)

Math.atanh(x) 返回x的反雙曲正切(inverse hyperbolic tangent)

指數運算子

概念

ES2016新增了一個指數運算子(**);

案例講解/注意事項

2 ** 2 // 4

2 ** 3 // 8

這個運算子的一個特點是右結合,而不是常見的左結合。多個指數運算子連用時,是從最右邊開始計算的。

// 相當於 2 ** (3 ** 2)

2 ** 3 ** 2

// 512

指數運算子可以與等號結合,形成一個新的賦值運算子(**=)。

let a = 1.5;

a **= 2;

// 等同於 a = a * a;

let b = 4;

b **= 3;

// 等同於 b = b * b * b;

BigInt資料型別

概念

JS所有數字都儲存成64位浮點數,這給數值的表示帶來了2個限制:

①數值的精度只能到 53 個二進位制位(相當於16個十進位制位),大於這個範圍的整數,JS是無法精確表示的,這使得JS不適合進行科學和金融方面的精確計算;

②大於或等於2的1024次方的數值,JS無法表示,會返回Infinity;

為了解決這個問題,ES2020 引入了一種新的資料型別 BigInt(大整數)來解決這個問題,這是ECMAScript的第8種資料型別;BigInt只用來表示整數,沒有位數的限制,任何位數的整數都可以精確表示;

案例講解/注意事項

const a = 2172141653n;

const b = 15346349309n;

// BigInt 可以保持精度

a * b // 33334444555566667777n

// 普通整數無法保持精度

Number(a) * Number(b) // 33334444555566670000

為了與 Number 型別區別,BigInt 型別的資料必須新增字尾n

1234 // 普通整數

1234n // BigInt

// BigInt 的運算

1n + 2n // 3n

BigInt 同樣可以使用各種進製表示,都要加上字尾n

0b1101n // 二進位制

0o777n // 八進位制

0xFFn // 十六進位制

BigInt與普通整數是兩種值,它們之間並不相等

42n === 42 // false

typeof運算子對於 BigInt 型別的資料返回bigint。

typeof 123n // 'bigint'

BigInt 可以使用負號(-),但是不能使用正號(+),因為會與 asm.js 衝突。

-42n // 正確

+42n // 報錯

JS以前不能計算70的階乘(即70!),因為超出了可以表示的精度。

let p = 1n;

for (let i = 1n; i <= 70n; i++) {

p *= i;

}

console.log(p); // 11978571...00000000n

BigInt物件

概念

JS原生提供BigInt物件,可以用作建構函式生成 BigInt 型別的數值,轉換規則基本與Number()一致,將其他型別的值轉為 BigInt;

BigInt()建構函式必須有引數,而且引數必須可以正常轉為數值,並且引數如果是小數,則會報錯;

BigInt 物件繼承了 Object 物件的兩個例項方法:

BigInt.prototype.toString()

BigInt.prototype.valueOf()

它還繼承了 Number 物件的一個例項方法:

BigInt.prototype.toLocaleString()

此外,還提供了三個靜態方法:

①BigInt.asUintN(width, BigInt): 給定的 BigInt 轉為 0 到 2width - 1 之間對應的值。

②BigInt.asIntN(width, BigInt):給定的 BigInt 轉為 -2width - 1 到 2width - 1 - 1 之間對應的值。

③BigInt.parseInt(string[, radix]):近似於Number.parseInt(),將一個字串轉換成指定進位制的 BigInt。

案例講解/注意事項

BigInt(123) // 123n

BigInt('123') // 123n

BigInt(false) // 0n

BigInt(true) // 1n

const max = 2n ** (64n - 1n) - 1n;

BigInt.asIntN(64, max)

// 9223372036854775807n

BigInt.asIntN(64, max + 1n)

// -9223372036854775808n

BigInt.asUintN(64, max + 1n)

// 9223372036854775808n

max是64位帶符號的 BigInt 所能表示的最大值。如果對這個值加1n,BigInt.asIntN()將會返回一個負值,因為這時新增的一位將被解釋為符號位。而BigInt.asUintN()方法由於不存在符號位,所以可以正確返回結果;

const max = 2n ** (64n - 1n) - 1n;

BigInt.asIntN(32, max) // -1n

BigInt.asUintN(32, max) // 4294967295n

如果BigInt.asIntN()和BigInt.asUintN()指定的位數,小於數值本身的位數,那麼頭部的位將被捨棄;max是一個64位的 BigInt,如果轉為32位,前面的32位都會被捨棄。

// Number.parseInt() 與 BigInt.parseInt() 的對比

Number.parseInt('9007199254740993', 10)

// 9007199254740992

BigInt.parseInt('9007199254740993', 10)

// 9007199254740993n

由於有效數字超出了最大限度,Number.parseInt方法返回的結果是不精確的,而BigInt.parseInt方法正確返回了對應的 BigInt;對於二進位制陣列,BigInt 新增了兩個型別BigUint64Array和BigInt64Array,這兩種資料型別返回的都是64位 BigInt。DataView物件的例項方法DataView.prototype.getBigInt64()和DataView.prototype.getBigUint64(),返回的也是 BigInt;

其他運算

可以使用Boolean()、Number()和String()這三個方法,將 BigInt 可以轉為布林值、數值和字串型別:

Boolean(0n) // false

Boolean(1n) // true

Number(1n) // 1

String(1n) // "1"

上面程式碼中,注意最後一個例子,轉為字串時後綴n會消失。

另外,取反運算子(!)也可以將 BigInt 轉為布林值。

!0n // true

!1n // false

數學運算

數學運算方面,BigInt 型別的+、-、*和**這四個二元運算子,與 Number 型別的行為一致。除法運算/會捨去小數部分,返回一個整數;

幾乎所有的數值運算子都可以用在 BigInt,但是有兩個例外。

①不帶符號的右移位運算子>>>

②一元的求正運算子+

>>>運算子是不帶符號的,但是 BigInt總是帶有符號的,導致該運算無意義,完全等同於右移運算子>>。後者是因為一元運算子+在 asm.js 裡面總是返回 Number 型別,為了不破壞 asm.js 就規定+1n會報錯。

BigInt 不能與普通數值進行混合運算。

1n + 1.3 // 報錯

(2n**53n + 1n) + 0.5這個表示式,如果返回 BigInt 型別,0.5這個小數部分會丟失;如果返回 Number 型別,有效精度只能保持 53 位,導致精度下降。

同樣的原因,如果一個標準庫函式的引數預期是 Number 型別,但是得到的是一個 BigInt,就會報錯。

// 錯誤的寫法

Math.sqrt(4n) // 報錯

// 正確的寫法

Math.sqrt(Number(4n)) // 2

上面程式碼中,Math.sqrt的引數預期是 Number 型別,如果是 BigInt 就會報錯,必須先用Number方法轉一下型別,才能進行計算。

BigInt 對應的布林值,與 Number 型別一致,即0n會轉為false,其他值轉為true。

if (0n) {

console.log('if');

} else {

console.log('else');

}

// else

上面程式碼中,0n對應false,所以會進入else子句。

比較運算子(比如>)和相等運算子(==)允許 BigInt 與其他型別的值混合計算,因為這樣做不會損失精度。

0n < 1 // true

0n < true // true

0n == 0 // true

0n == false // true

0n === 0 // false

BigInt 與字串混合運算時,會先轉為字串,再進行運算。

'' + 123n // "123"