您現在的位置是:首頁 > 武術
詳述JavaScript的數字型別與數字系統
- 由 龍騎士程式設計 發表于 武術
- 2021-05-25
的傷勢12被除數和除數和為377 這個算式是多少
在現代 JavaScript 中,數字(number)有兩種型別:
JavaScript 中的常規數字以 64 位的格式 IEEE-754 儲存,也被稱為“雙精度浮點數”。這是我們大多數時候所使用的數字,我們將在本章中學習它們。
BigInt 數字,用於表示任意長度的整數。有時會需要它們,因為常規數字不能超過
253
或小於
-253
。由於僅在少數特殊領域才會用到 BigInt,因此我們在特殊的章節 BigInt 中對其進行了介紹。
所以,在這裡我們將討論常規數字型別。現在讓我們開始學習吧。
一、編寫數字的更多方法
想象一下,我們需要寫 10 億。顯然的方法是:
let billion = 1000000000;
但在現實生活中,我們通常避免寫一長串零,因為它很容易打錯。另外,我們很懶。我們通常會將 10 億寫成
“1bn”
,或將 73 億寫成
“7。3bn”
。對於大多數大的數字來說都是如此。
在 JavaScript 中,我們透過在數字後附加字母 “e”,並指定零的數量來縮短數字:
let billion = 1e9; // 10 億,字面意思:數字 1 後面跟 9 個 0alert( 7。3e9 ); // 73 億(7,300,000,000)
換句話說,
“e”
把數字乘以
1
後面跟著給定數量的 0 的數字。
1e3 = 1 * 10001。23e6 = 1。23 * 1000000
現在讓我們寫一些非常小的數字。例如,1 微秒(百萬分之一秒):
let ms = 0。000001;
就像以前一樣,可以使用
“e”
來完成。如果我們想避免顯式地寫零,我們可以這樣寫:
let ms = 1e-6; // 1 的左邊有 6 個 0
如果我們數一下
0。000001
中的 0 的個數,是 6 個。所以自然是
1e-6
。
換句話說,
e
後面的負數表示除以 1 後面跟著給定數量的 0 的數字:
// -3 除以 1 後面跟著 3 個 0 的數字1e-3 = 1 / 1000 (=0。001)// -6 除以 1 後面跟著 6 個 0 的數字1。23e-6 = 1。23 / 1000000 (=0。00000123)
二、十六進位制,二進位制和八進位制數字
十六進位制 數字在 JavaScript 中被廣泛用於表示顏色,編碼字元以及其他許多東西。所以自然地,有一種較短的寫方法:
0x
,然後是數字。
例如:
alert( 0xff ); // 255alert( 0xFF ); // 255(一樣,大小寫沒影響)
二進位制和八進位制數字系統很少使用,但也支援使用
0b
和
0o
字首:
let a = 0b11111111; // 二進位制形式的 255let b = 0o377; // 八進位制形式的 255alert( a == b ); // true,兩邊是相同的數字,都是 255
只有這三種進位制支援這種寫法。對於其他進位制,我們應該使用函式
parseInt
(我們將在本章後面看到)。
三、toString(base)
方法
num。toString(base)
返回在給定
base
進位制數字系統中
num
的字串表示形式。
舉個例子:
let num = 255;alert( num。toString(16) ); // ffalert( num。toString(2) ); // 11111111
base
的範圍可以從
2
到
36
。預設情況下是
10
。
常見的用例如下:
base=16
用於十六進位制顏色,字元編碼等,數字可以是
0。。9
或
A。。F
。
base=2
主要用於除錯按位操作,數字可以是
0
或
1
。
base=36
是最大進位制,數字可以是
0。。9
或
A。。Z
。所有拉丁字母都被用於了表示數字。對於
36
進位制來說,一個有趣且有用的例子是,當我們需要將一個較長的數字識別符號轉換成較短的時候,例如做一個短的 URL。可以簡單地使用基數為
36
的數字系統表示:alert
(
123456。
。
toString
(
36
)
);
// 2n9c
使用兩個點來呼叫一個方法
請注意
123456。。toString(36)
中的兩個點不是打錯了。如果我們想直接在一個數字上呼叫一個方法,比如上面例子中的
toString
,那麼我們需要在它後面放置兩個點
。。
。
如果我們放置一個點:
123456。toString(36)
,那麼就會出現一個 error,因為 JavaScript 語法隱含了第一個點之後的部分為小數部分。如果我們再放一個點,那麼 JavaScript 就知道小數部分為空,現在使用該方法。
也可以寫成
(123456)。toString(36)
。
四、舍入
舍入(rounding)是使用數字時最常用的操作之一。
這裡有幾個對數字進行舍入的內建函式:
Math。floor
向下舍入:
3。1
變成
3
,
-1。1
變成
-2
。
Math。ceil
向上舍入:
3。1
變成
4
,
-1。1
變成
-1
。
Math。round
向最近的整數舍入:
3。1
變成
3
,
3。6
變成
4
,
-1。1
變成
-1
。
Math。trunc
(IE 瀏覽器不支援這個方法)移除小數點後的所有內容而沒有舍入:
3。1
變成
3
,
-1。1
變成
-1
。
這個是總結它們之間差異的表格:
這些函式涵蓋了處理數字小數部分的所有可能方法。但是,如果我們想將數字舍入到小數點後
n
位,該怎麼辦?
例如,我們有
1。2345
,並且想把它舍入到小數點後兩位,僅得到
1。23
。
有兩種方式可以實現這個需求:
乘除法例如,要將數字舍入到小數點後兩位,我們可以將數字乘以
100
(或更大的 10 的整數次冪),呼叫舍入函式,然後再將其除回。
let num = 1。23456;alert( Math。floor(num * 100) / 100 ); // 1。23456 -> 123。456 -> 123 -> 1。23
函式 toFixed(n) 將數字舍入到小數點後
n
位,並以字串形式返回結果。
let num = 12。34;alert( num。toFixed(1) ); // “12。3”
這會向上或向下舍入到最接近的值,類似於 Math。round:
let num = 12。36;alert( num。toFixed(1) ); // “12。4”
“請注意 toFixed 的結果是一個字串。如果小數部分比所需要的短,則在結尾新增零:
let num = 12。34;alert( num。toFixed(5) ); // ”12。34000“,在結尾添加了 0,以達到小數點後五位
我們可以使用一元加號或 Number() 呼叫,將其轉換為數字:+ num。toFixed(5)。
五、不精確的計算
在內部,數字是以 64 位格式 IEEE-754 表示的,所以正好有 64 位可以儲存一個數字:其中 52 位被用於儲存這些數字,其中 11 位用於儲存小數點的位置(對於整數,它們為零),而 1 位用於符號。
如果一個數字太大,則會溢位 64 位儲存,並可能會導致無窮大:
alert( 1e500 ); // Infinity
這可能不那麼明顯,但經常會發生的是,精度的損失。
考慮下這個(falsy!)測試:
alert( 0。1 + 0。2 == 0。3 ); // false
沒錯,如果我們檢查
0。1
和
0。2
的總和是否為
0。3
,我們會得到
false
。
奇了怪了!如果不是
0。3
,那能是啥?
alert( 0。1 + 0。2 ); // 0。30000000000000004
哎喲!這個錯誤比不正確的比較的後果更嚴重。想象一下,你建立了一個電子購物網站,如果訪問者將價格為
¥ 0。10
和
¥ 0。20
的商品放入了他的購物車。訂單總額將是
¥ 0。30000000000000004
。這會讓任何人感到驚訝。
但為什麼會這樣呢?
一個數字以其二進位制的形式儲存在記憶體中,一個 1 和 0 的序列。但是在十進位制數字系統中看起來很簡單的
0。1
,
0。2
這樣的小數,實際上在二進位制形式中是無限迴圈小數。
換句話說,什麼是
0。1
?
0。1
就是
1
除以
10
,
1/10
,即十分之一。在十進位制數字系統中,這樣的數字表示起來很容易。將其與三分之一進行比較:
1/3
。三分之一變成了無限迴圈小數
0。33333(3)
。
在十進位制數字系統中,可以保證以
10
的整數次冪作為除數能夠正常工作,但是以
3
作為除數則不能。也是同樣的原因,在二進位制數字系統中,可以保證以
2
的整數次冪作為除數時能夠正常工作,但
1/10
就變成了一個無限迴圈的二進位制小數。
使用二進位制數字系統無法
精確
儲存
0。1
或
0。2
,就像沒有辦法將三分之一儲存為十進位制小數一樣。
IEEE-754 數字格式透過將數字舍入到最接近的可能數字來解決此問題。這些舍入規則通常不允許我們看到“極小的精度損失”,但是它確實存在。
我們可以看到:
alert( 0。1。toFixed(20) ); // 0。10000000000000000555
當我們對兩個數字進行求和時,它們的“精度損失”會疊加起來。
這就是為什麼
0。1 + 0。2
不等於
0。3
。
不僅僅是 JavaScript
許多其他程式語言也存在同樣的問題。
PHP,Java,C,Perl,Ruby 給出的也是完全相同的結果,因為它們基於的是相同的數字格式。
我們能解決這個問題嗎?當然,最可靠的方法是藉助方法 toFixed(n) 對結果進行舍入:
let sum = 0。1 + 0。2;alert( sum。toFixed(2) ); // 0。30
請注意,
toFixed
總是返回一個字串。它確保小數點後有 2 位數字。如果我們有一個電子購物網站,並需要顯示
¥ 0。30
,這實際上很方便。對於其他情況,我們可以使用一元加號將其強制轉換為一個數字:
let sum = 0。1 + 0。2;alert( +sum。toFixed(2) ); // 0。3
我們可以將數字臨時乘以 100(或更大的數字),將其轉換為整數,進行數學運算,然後再除回。當我們使用整數進行數學運算時,誤差會有所減少,但仍然可以在除法中得到:
alert( (0。1 * 10 + 0。2 * 10) / 10 ); // 0。3alert( (0。28 * 100 + 0。14 * 100) / 100); // 0。4200000000000001
因此,乘/除法可以減少誤差,但不能完全消除誤差。
有時候我們可以嘗試完全避免小數。例如,我們正在建立一個電子購物網站,那麼我們可以用角而不是元來儲存價格。但是,如果我們要打 30% 的折扣呢?實際上,完全避免小數處理幾乎是不可能的。只需要在必要時剪掉其“尾巴”來對其進行舍入即可。
有趣的事兒
嘗試執行下面這段程式碼:
// Hello!我是一個會自我增加的數字!alert( 9999999999999999 ); // 顯示 10000000000000000
出現了同樣的問題:精度損失。有 64 位來表示該數字,其中 52 位可用於儲存數字,但這還不夠。所以最不重要的數字就消失了。
JavaScript 不會在此類事件中觸發 error。它會盡最大努力使數字符合所需的格式,但不幸的是,這種格式不夠大到滿足需求。
兩個零
數字內部表示的另一個有趣結果是存在兩個零:
0
和
-0
。
這是因為在儲存時,使用一位來儲存符號,因此對於包括零在內的任何數字,可以設定這一位或者不設定。
在大多數情況下,這種區別並不明顯,因為運算子將它們視為相同的值。
六、測試:isFinite 和 isNaN
還記得這兩個特殊的數值嗎?
Infinity
(和
-Infinity
)是一個特殊的數值,比任何數值都大(小)。
NaN
代表一個 error。
它們屬於
number
型別,但不是“普通”數字,因此,這裡有用於檢查它們的特殊函式:
isNaN(value)
將其引數轉換為數字,然後測試它是否為
alert( isNaN(NaN) ); // truealert( isNaN(”str“) ); // true
但是我們需要這個函式嗎?我們不能只使用 === NaN 比較嗎?不好意思,這不行。值 “NaN” 是獨一無二的,它不等於任何東西,包括它自身:
alert( NaN === NaN ); // false
isFinite(value)
將其引數轉換為數字,如果是常規數字,則返回
true
,而不是
NaN/Infinity/-Infinity
:
alert( isFinite(”15“) ); // truealert( isFinite(”str“) ); // false,因為是一個特殊的值:NaNalert( isFinite(Infinity) ); // false,因為是一個特殊的值:Infinity
有時
isFinite
被用於驗證字串值是否為常規數字:
let num = +prompt(”Enter a number“, ‘’);// 結果會是 true,除非你輸入的是 Infinity、-Infinity 或不是數字alert( isFinite(num) );
請注意,在所有數字函式中,包括
isFinite
,空字串或僅有空格的字串均被視為
0
。
與
Object。is
進行比較
有一個特殊的內建方法
Object。is
,它類似於
===
一樣對值進行比較,但它對於兩種邊緣情況更可靠:
它適用於
NaN
:
Object。is(NaN,NaN)=== true
,這是件好事。
值
0
和
-0
是不同的:
Object。is(0,-0)=== false
,從技術上講這是對的,因為在內部,數字的符號位可能會不同,即使其他所有位均為零。
在所有其他情況下,
Object。is(a,b)
與
a === b
相同。
這種比較方式經常被用在 JavaScript 規範中。當內部演算法需要比較兩個值是否完全相同時,它使用
Object。is
(內部稱為 SameValue)。
七、parseInt 和 parseFloat
使用加號
+
或
Number()
的數字轉換是嚴格的。如果一個值不完全是一個數字,就會失敗:
alert( +”100px“ ); // NaN
唯一的例外是字串開頭或結尾的空格,因為它們會被忽略。
但在現實生活中,我們經常會有帶有單位的值,例如 CSS 中的
”100px“
或
”12pt“
。並且,在很多國家,貨幣符號是緊隨金額之後的,所以我們有
”19€“
,並希望從中提取出一個數值。
這就是
parseInt
和
parseFloat
的作用。
它們可以從字串中“讀取”數字,直到無法讀取為止。如果發生 error,則返回收集到的數字。函式
parseInt
返回一個整數,而
parseFloat
返回一個浮點數:
alert( parseInt(‘100px’) ); // 100alert( parseFloat(‘12。5em’) ); // 12。5alert( parseInt(‘12。3’) ); // 12,只有整數部分被返回了alert( parseFloat(‘12。3。4’) ); // 12。3,在第二個點出停止了讀取
某些情況下,
parseInt/parseFloat
會返回
NaN
。當沒有數字可讀時會發生這種情況:
alert( parseInt(‘a123’) ); // NaN,第一個符號停止了讀取
parseInt(str, radix)` 的第二個引數
parseInt()
函式具有可選的第二個引數。它指定了數字系統的基數,因此
parseInt
還可以解析十六進位制數字、二進位制數字等的字串:
alert( parseInt(‘0xff’, 16) ); // 255alert( parseInt(‘ff’, 16) ); // 255,沒有 0x 仍然有效alert( parseInt(‘2n9c’, 36) ); // 123456
八、其他數學函式
JavaScript 有一個內建的 Math 物件,它包含了一個小型的數學函式和常量庫。
幾個例子:
Math。random()
返回一個從 0 到 1 的隨機數(不包括 1)
alert( Math。random() ); // 0。1234567894322alert( Math。random() ); // 0。5435252343232alert( Math。random() ); // 。。。 (任何隨機數)
Math。max(a, b, c。。。)
/
Math。min(a, b, c。。。)
從任意數量的引數中返回最大/最小值。
alert( Math。max(3, 5, -10, 0, 1) ); // 5alert( Math。min(1, 2) ); // 1
Math。pow(n, power)
返回
n
的給定(power)次冪
alert( Math。pow(2, 10) ); // 2 的 10 次冪 = 1024
Math
物件中還有更多函式和常量,包括三角函式,你可以在 Math 物件文件 中找到這些內容。
九、總結
要寫有很多零的數字:
將
”e“
和 0 的數量附加到數字後。就像:
123e6
與
123
後面接 6 個 0 相同。
”e“
後面的負數將使數字除以 1 後面接著給定數量的零的數字。例如
123e-6
表示
0。000123
(
123
的百萬分之一)。
對於不同的數字系統:
可以直接在十六進位制(
0x
),八進位制(
0o
)和二進位制(
0b
)系統中寫入數字。
parseInt(str,base)
將字串
str
解析為在給定的
base
數字系統中的整數,
2 ≤ base ≤ 36
。
num。toString(base)
將數字轉換為在給定的
base
數字系統中的字串。
要將
12pt
和
100px
之類的值轉換為數字:
使用
parseInt/parseFloat
進行“軟”轉換,它從字串中讀取數字,然後返回在發生 error 前可以讀取到的值。
小數:
使用
Math。floor
,
Math。ceil
,
Math。trunc
,
Math。round
或
num。toFixed(precision)
進行舍入。
請確保記住使用小數時會損失精度。
更多數學函式:
需要時請檢視 Math 物件。這個庫很小,但是可以滿足基本的需求。