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" |