php數字兩位小數_詳述JavaScript的數字型別與數字系統
技術標籤:php數字兩位小數
在現代 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 物件。這個庫很小,但是可以滿足基本的需求。