Trong JavaScript hiện đại, có hai loại số:
- Các số thông thường trong JavaScript được lưu trữ ở định dạng 64 bit IEEE-754 , còn được gọi là các số dấu phẩy động chính xác kép. Đây là những con số chúng ta đang sử dụng hầu hết thời gian và chúng ta sẽ nói về chúng trong chương này.
- Số BigInt, để biểu thị số nguyên có độ dài tùy ý. Chúng đôi khi cần thiết, bởi vì một số lượng thông thường không thể vượt quá hoặc ít hơn. Vì các gợi ý được sử dụng trong một số lĩnh vực đặc biệt, chúng ta dành cho họ một chương đặc biệt BigInt .
253-253
Vì vậy, ở đây chúng ta sẽ nói về những con số thông thường. Hãy mở rộng kiến thức của chúng ta về chúng.
Nội dung chính
1. Nhiều cách để viết một số
Hãy tưởng tượng chúng ta cần viết 1 tỷ. Cách rõ ràng là:
let billion = 1000000000;
Nhưng trong cuộc sống thực, chúng ta thường tránh viết một chuỗi số 0 dài vì dễ gõ nhầm. Ngoài ra, chúng ta lười biếng. Chúng ta thường sẽ viết một cái gì đó như "1bn"
cho một tỷ hoặc "7.3bn"
7 tỷ 300 triệu. Điều này cũng đúng với hầu hết các số lượng lớn.
Trong JavaScript, chúng ta rút ngắn một số bằng cách nối thêm chữ cái "e"
vào số đó và chỉ định số không:
let billion = 1e9; // 1 billion, literally: 1 and 9 zeroes
alert( 7.3e9 ); // 7.3 billions (7,300,000,000)
Nói cách khác, "e"
là nhân số đó với 1
và 1 lượng số 0 đã cho.
1e3 = 1 * 1000
1.23e6 = 1.23 * 1000000
Bây giờ hãy viết một cái gì đó rất nhỏ. Nói, 1 micro giây (một phần triệu giây):
let ms = 0.000001;
Cũng giống như trước đây, sử dụng "e"
có thể giúp đỡ. Nếu chúng ta muốn tránh viết các số 0 một cách rõ ràng, chúng ta có thể nói giống như:
let ms = 1e-6; // six zeroes to the left from 1
Nếu chúng ta đếm các số 0 trong 0.000001
đó, có 6 trong số chúng. Vì vậy, tự nhiên nó 1e-6
.
Nói cách khác, số âm sau "e"
có nghĩa là 1 chia với 1 và 1 số lượng số 0 đã cho:
/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
*/
// -3 divides by 1 with 3 zeroes
1e-3 = 1 / 1000 (=0.001)
// -6 divides by 1 with 6 zeroes
1.23e-6 = 1.23 / 1000000 (=0.00000123)
2. Số hex, nhị phân và số bát phân
Số thập lục phân được sử dụng rộng rãi trong JavaScript để thể hiện màu sắc, mã hóa ký tự và cho nhiều thứ khác. Vì vậy, một cách tự nhiên, tồn tại một cách ngắn hơn để viết chúng: 0x
và sau đó là số.
Ví dụ:
alert( 0xff ); // 255
alert( 0xFF ); // 255 (the same, case doesn't matter)
Hệ thống số nhị phân và số bát phân hiếm khi được sử dụng, nhưng cũng được hỗ trợ bằng cách sử dụng tiền tố 0b
và 0o
:
let a = 0b11111111; // binary form of 255
let b = 0o377; // octal form of 255
alert( a == b ); // true, the same number 255 at both sides
Chỉ có 3 hệ thống số với sự hỗ trợ như vậy. Đối với các hệ thống số khác, chúng ta nên sử dụng hàm parseInt
(mà chúng ta sẽ thấy sau trong chương này).
3. toString (base)
Phương thức num.toString(base)
trả về một chuỗi đại diện num
trong hệ thống số với số đã cho base
.
Ví dụ:
/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/
let num = 255;
alert( num.toString(16) ); // ff
alert( num.toString(2) ); // 11111111
Có base
thể thay đổi từ 2
đến 36
. Theo mặc định, nó là 10
.
Các trường hợp sử dụng phổ biến cho việc này là:
-
base = 16 được sử dụng cho màu hex, mã hóa ký tự, v.v., chữ số có thể
0..9
hoặcA..F
. -
base = 2 chủ yếu để gỡ lỗi các thao tác bitwise, các chữ số có thể
0
hoặc1
. -
cơ sở = 36 là tối đa, chữ số có thể
0..9
hoặcA..Z
. Toàn bộ bảng chữ cái Latin được sử dụng để đại diện cho một số. Một trường hợp hài hước nhưng hữu ích36
là khi chúng ta cần biến một số nhận dạng số dài thành một cái gì đó ngắn hơn, ví dụ để tạo một url ngắn. Có thể chỉ cần biểu diễn nó trong hệ thống số với cơ sở36
:alert( 123456..toString(36) ); // 2n9c
Hai dấu chấm để gọi một phương thức
Xin lưu ý rằng hai dấu chấm trong 123456..toString(36)
không phải là một lỗi đánh máy. Nếu chúng ta muốn gọi một phương thức trực tiếp trên một số, như toString
trong ví dụ trên, thì chúng ta cần đặt hai dấu chấm ..
sau nó.
Nếu chúng ta đặt một dấu chấm đơn: 123456.toString(36)
thì sẽ có lỗi, vì cú pháp JavaScript ngụ ý phần thập phân sau dấu chấm đầu tiên. Và nếu chúng ta đặt thêm một dấu chấm, thì JavaScript biết rằng phần thập phân trống và bây giờ đi theo sau là phương thức.
Cũng có thể viết (123456).toString(36)
.
4. Làm tròn
Một trong những thao tác được sử dụng nhiều nhất khi làm việc với các số là làm tròn số.
Có một số chức năng tích hợp để làm tròn:
Math.floor
Làm tròn xuống: 3.1
trở thành 3
, và -1.1
trở thành -2
.
Math.ceil
Làm tròn lên: 3.1
trở thành 4
, và -1.1
trở thành -1
.
Math.round
Làm tròn đến số nguyên gần nhất: 3.1
trở thành 3
, 3.6
trở thành 4
và -1.1
trở thành -1
.
Math.trunc
(không được Internet Explorer hỗ trợ)
Loại bỏ bất cứ điều gì sau dấu thập phân mà không làm tròn: 3.1
trở thành 3
, -1.1
trở thành -1
.
Đây là bảng để tóm tắt sự khác biệt giữa chúng:
Math.floor | Math.ceil | Math.round | Math.trunc | |
---|---|---|---|---|
3.1 | 3 | 4 | 3 | 3 |
3.6 | 3 | 4 | 4 | 3 |
-1.1 | -2 | -1 | -1 | -1 |
-1.6 | -2 | -1 | -2 | -1 |
Các hàm này bao gồm tất cả các cách có thể để xử lý phần thập phân của một số. Nhưng nếu chúng ta muốn làm tròn số thành n-th
chữ số sau số thập phân thì sao?
Chẳng hạn, chúng ta có 1.2345
và muốn làm tròn nó thành 2 chữ số, chỉ nhận được 1.23
.
Có hai cách để làm như vậy:
- Nhân và chia. Ví dụ, để làm tròn số thành chữ số thứ 2 sau số thập phân, chúng ta có thể nhân số đó với
100
, gọi hàm làm tròn và sau đó chia lại.
let num = 1.23456;
alert( Math.floor(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
Phương thức toFixed(n) làm tròn số thành n
chữ số sau phần thập phân và trả về một chuỗi đại diện cho kết quả.
let num = 12.34;
alert( num.toFixed(1) ); // "12.3"
Điều này làm tròn lên hoặc xuống giá trị gần nhất, tương tự như Math.round
:
let num = 12.36;
alert( num.toFixed(1) ); // "12.4"
Xin lưu ý rằng kết quả toFixed
là một chuỗi. Nếu phần thập phân ngắn hơn yêu cầu, số 0 được thêm vào cuối:
let num = 12.34;
alert( num.toFixed(5) ); // "12.34000", added zeroes to make exactly 5 digits
- Chúng ta có thể chuyển đổi nó thành một số bằng cách sử dụng dấu cộng hoặc một
Number()
cuộc gọi :+num.toFixed(5)
.
5. Tính toán tạm thời
Trong nội bộ, một số được biểu thị ở định dạng 64 bit IEEE-754 , do đó, có chính xác 64 bit để lưu trữ một số: 52 trong số chúng được sử dụng để lưu trữ các chữ số, 11 trong số chúng lưu trữ vị trí của dấu thập phân (chúng bằng 0 cho số nguyên) và 1 bit là dấu.
Nếu một số quá lớn, nó sẽ tràn bộ nhớ 64 bit, có khả năng mang lại vô hạn:
alert( 1e500 ); // Infinity
Điều có thể ít rõ ràng hơn một chút, nhưng xảy ra khá thường xuyên, là sự mất độ chính xác.
Hãy xem xét bài kiểm tra này (giả mạo!):
alert( 0.1 + 0.2 == 0.3 ); // false
Điều đó đúng, nếu chúng ta kiểm tra xem tổng 0.1
và 0.2
có 0.3
, chúng ta sẽ nhận được false
.
Lạ thật! Nó là gì sau đó nếu không là 0.3
?
alert( 0.1 + 0.2 ); // 0.30000000000000004
Ôi! Có nhiều hậu quả hơn so với một so sánh không chính xác ở đây. Hãy tưởng tượng bạn đang làm cho một trang web mua sắm và khách đặt$0.10
và $0.20
hàng hóa vào giỏ hàng của họ. Tổng số đơn đặt hàng sẽ được $0.30000000000000004
. Điều đó sẽ làm bất ngờ bất cứ ai.
Nhưng tại sao điều này xảy ra?
Một số được lưu trữ trong bộ nhớ ở dạng nhị phân của nó, một chuỗi các bit – số và số 0. Nhưng các phân số như 0.1
, 0.2
trông đơn giản trong hệ thống số thập phân thực sự là các phân số không có hồi kết ở dạng nhị phân của chúng.
Nói cách khác, là 0.1
gì? Nó là một chia cho mười 1/10
, một phần mười. Trong hệ thống số thập phân, số đó dễ dàng được biểu diễn. So sánh nó với một phần ba : 1/3
. Nó trở thành một phần vô tận 0.33333(3)
.
Vì vậy, phân chia theo quyền hạn 10
được đảm bảo hoạt động tốt trong hệ thập phân, nhưng phân chia theo 3
thì không. Vì lý do tương tự, trong hệ thống số nhị phân, sự phân chia theo quyền hạn 2
được đảm bảo hoạt động, nhưng 1/10
trở thành một phần nhị phân vô tận.
Không có cách nào để lưu trữ chính xác 0,1 hoặc chính xác 0,2 bằng hệ thống nhị phân, giống như không có cách nào để lưu trữ một phần ba dưới dạng phân số thập phân.
Định dạng số IEEE-754 giải quyết điều này bằng cách làm tròn đến số gần nhất có thể. Những quy tắc làm tròn này thường không cho phép chúng ta thấy rằng mất một chút chính xác, nhưng nó tồn tại.
Chúng ta có thể thấy điều này trong hành động:
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
Và khi chúng ta tổng hợp hai số, tổn thất chính xác của họ, cộng lại.
Đó là lý do tại sao 0.1 + 0.2
không chính xác 0.3
.
Không chỉ JavaScript
Vấn đề tương tự tồn tại trong nhiều ngôn ngữ lập trình khác.
PHP, Java, C, Perl, Ruby cho kết quả chính xác như nhau, vì chúng dựa trên cùng một định dạng số.
Chúng ta có thể làm việc xung quanh vấn đề? Chắc chắn, phương pháp đáng tin cậy nhất là làm tròn kết quả với sự trợ giúp của phương thức toFixed(n):
let sum = 0.1 + 0.2;
alert( sum.toFixed(2) ); // 0.30
Xin lưu ý rằng toFixed
luôn luôn trả về một chuỗi. Nó đảm bảo rằng nó có 2 chữ số sau dấu thập phân. Điều đó thực sự tiện lợi nếu chúng ta có một mua sắm điện tử và cần hiển thị $0.30
. Đối với các trường hợp khác, chúng ta có thể sử dụng dấu cộng đơn để ép buộc nó thành một số:
let sum = 0.1 + 0.2;
alert( +sum.toFixed(2) ); // 0.3
Chúng ta cũng có thể tạm thời nhân các số với 100 (hoặc một số lớn hơn) để biến chúng thành số nguyên, làm toán và sau đó chia lại. Sau đó, khi chúng ta đang làm toán với các số nguyên, lỗi sẽ giảm đi phần nào, nhưng chúng tôi vẫn nhận được nó trên phép chia:
alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3
alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001
Vì vậy, phương pháp nhân / chia làm giảm lỗi, nhưng không loại bỏ hoàn toàn.
Đôi khi chúng ta có thể cố gắng trốn tránh các phân số. Giống như nếu chúng ta giao dịch với một cửa hàng, thì chúng ta có thể lưu trữ giá bằng xu thay vì đô la. Nhưng nếu chúng ta áp dụng giảm giá 30% thì sao? Trong thực tế, hiếm khi trốn tránh phân số là hiếm khi có thể. Chỉ cần làm tròn chúng để cắt đuôi của bạn khi cần.
Điều buồn cười ở đây
Hãy thử chạy nó:
// Hello! I'm a self-increasing number!
alert( 9999999999999999 ); // shows 10000000000000000
Điều này bị cùng một vấn đề: mất độ chính xác. Có 64 bit cho số, 52 trong số chúng có thể được sử dụng để lưu trữ các chữ số, nhưng điều đó là không đủ. Vì vậy, các chữ số ít quan trọng nhất biến mất.
JavaScript không gây ra lỗi trong các sự kiện như vậy. Nó làm hết sức mình để phù hợp với số vào định dạng mong muốn, nhưng thật không may, định dạng này không đủ lớn.
Hai số không
Một hậu quả buồn cười khác của biểu diễn bên trong của các con số là sự tồn tại của hai số không: 0
và -0
.
Đó là bởi vì một dấu được biểu thị bằng một bit đơn, do đó, nó có thể được đặt hoặc không được đặt cho bất kỳ số nào kể cả số không.
Trong hầu hết các trường hợp, sự khác biệt là không đáng chú ý, bởi vì các toán tử phù hợp để coi chúng là như nhau.
6. Các xét nghiệm: isFinite và isNaN
Ghi nhớ hai giá trị số đặc biệt này?
Infinity
(và-Infinity
) là một giá trị số đặc biệt lớn hơn (ít hơn) bất cứ thứ gì.NaN
đại diện cho một lỗi.
Chúng thuộc loại number
, nhưng không phải là số thông thường, nên có các hàm đặc biệt để kiểm tra chúng:
-
isNaN(value)
chuyển đổi đối số của nó thành một số và sau đó kiểm tra xem nó có phải làNaN
:
alert( isNaN(NaN) ); // true alert( isNaN("str") ); // true
Nhưng chúng ta có cần hàm này không? Chúng ta không thể sử dụng so sánh === NaN
? Xin lỗi, nhưng câu trả lời là không. Giá trị NaN
này là duy nhất ở chỗ nó không bằng bất cứ thứ gì, kể cả chính nó:
alert( NaN === NaN ); // false
isFinite(value)
chuyển đổi đối số của nó thành một số và trả về true
nếu đó là một số thông thường, không phải NaN/Infinity/-Infinity
:
alert( isFinite("15") ); // true
alert( isFinite("str") ); // false, because a special value: NaN
alert( isFinite(Infinity) ); // false, because a special value: Infinity
Đôi khi isFinite
được sử dụng để xác thực xem giá trị chuỗi có phải là số thông thường hay không:
let num = +prompt("Enter a number", '');
// will be true unless you enter Infinity, -Infinity or not a number
alert( isFinite(num) );
Xin lưu ý rằng một chuỗi trống hoặc chỉ có khoảng trắng được xử lý như 0
trong tất cả các hàm số bao gồm isFinite
.
So sánh với Object.is
Có một phương thức tích hợp đặc biệt Object.is
để so sánh các giá trị như ===
, nhưng đáng tin cậy hơn cho hai trường hợp sau:
- Nó hoạt động với
NaN
:Object.is(NaN, NaN) === true
, đó là một điều tốt. - Giá trị
0
và-0
khác nhau :Object.is(0, -0) === false
, về mặt kỹ thuật là đúng, bởi vì bên trong con số có một bit dấu có thể khác nhau ngay cả khi tất cả các bit khác là số không.
Trong tất cả các trường hợp khác, Object.is(a, b)
giống như a === b
.
Cách so sánh này thường được sử dụng trong đặc tả JavaScript. Khi một thuật toán nội bộ cần so sánh hai giá trị để giống hệt nhau, nó sẽ sử dụng Object.is
(bên trong gọi là SameValue).
7. parseInt và parseFloat
Chuyển đổi số bằng cách sử dụng cộng +
hoặc Number()
là một cách nghiêm ngặt. Nếu một giá trị không chính xác là một số, nó sẽ thất bại:
alert( +"100px" ); // NaN
Ngoại lệ duy nhất là khoảng trắng ở đầu hoặc cuối chuỗi, vì chúng bị bỏ qua.
Nhưng trong cuộc sống thực, chúng ta thường có các giá trị theo đơn vị, như "100px"
hoặc "12pt"
trong CSS. Ngoài ra ở nhiều quốc gia, ký hiệu tiền tệ đi sau số tiền, vì vậy chúng tôi có "19€"
và muốn trích xuất một giá trị số từ đó.
Đó là những gì parseInt
và parseFloat
có thể làm.
Họ đã đọc một số từ một chuỗi cho đến khi họ không thể. Trong trường hợp có lỗi, số đã thu thập được trả về. Hàm parseInt
trả về một số nguyên, trong khi parseFloat
sẽ trả về số dấu phẩy động:
alert( parseInt('100px') ); // 100
alert( parseFloat('12.5em') ); // 12.5
alert( parseInt('12.3') ); // 12, only the integer part is returned
alert( parseFloat('12.3.4') ); // 12.3, the second point stops the reading
Có những tình huống khi nào parseInt/parseFloat
sẽ trở lại NaN
. Nó xảy ra khi không có chữ số nào có thể đọc được :
alert( parseInt('a123') ); // NaN, the first symbol stops the process
Đối số thứ hai của parseInt(str, radix)
Các hàm parseInt()
có một tham số tùy chọn thứ hai. Nó chỉ định cơ sở của hệ thống số, do đó parseInt
cũng có thể phân tích các chuỗi số hex, số nhị phân, v.v.
alert( parseInt('0xff', 16) ); // 255
alert( parseInt('ff', 16) ); // 255, without 0x also works
alert( parseInt('2n9c', 36) ); // 123456
8. Các hàm toán học khác
JavaScript có một đối tượng Math chứa một thư viện nhỏ các hàm và hằng số toán học.
Một vài ví dụ:Math.random()
Trả về một số ngẫu nhiên từ 0 đến 1 (không bao gồm 1)
alert( Math.random() ); // 0.1234567894322
alert( Math.random() ); // 0.5435252343232
alert( Math.random() ); // ... (any random numbers)
Math.max(a, b, c...)
/ Math.min(a, b, c...)
Trả về giá trị lớn nhất / nhỏ nhất từ số lượng đối số tùy ý.
alert( Math.max(3, 5, -10, 0, 1) ); // 5
alert( Math.min(1, 2) ); // 1
Math.pow(n, power)
Trả về n
tăng sức mạnh nhất định
alert( Math.pow(2, 10) ); // 2 in power 10 = 1024
Có nhiều hàm và hằng số trong đối tượngMath
, bao gồm cả lượng giác, mà bạn có thể tìm thấy trong các tài liệu cho đối tượng Math.
9. Tóm lược
Để viết số có nhiều số 0:
- Nối
"e"
với số 0. Giống như:123e6
giống như123
với 6 số 0:123000000
. - Một số âm sau khi
"e"
làm cho số đó được chia cho 1 với các số 0 đã cho. Ví dụ:123e-6
nghĩa là0.000123
(123
phần triệu).
Đối với các hệ thống số khác nhau:
- Có thể viết số trực tiếp trong các hệ hex (
0x
), bát phân (0o
) và nhị phân (0b
). parseInt(str, base)
phân tích chuỗistr
thành một số nguyên trong hệ thống số vớibase
,2 ≤ base ≤ 36
.num.toString(base)
chuyển đổi một số thành một chuỗi trong hệ thống số với số đã chobase
.
Để chuyển đổi các giá trị như 12pt
và 100px
thành một số:
- Sử dụng
parseInt/parseFloat
cho chuyển đổi thành phần, trong đó đọc một số từ một chuỗi và sau đó trả về giá trị họ có thể đọc trước khi xảy ra lỗi.
Đối với phân số:
- Vòng sử dụng
Math.floor
,Math.ceil
,Math.trunc
,Math.round
hoặcnum.toFixed(precision)
. - Đảm bảo nhớ rằng mất độ chính xác khi làm việc với phân số.
Một số hàm toán học khác:
- Xem đối tượng Math khi bạn cần chúng. Thư viện rất nhỏ, nhưng có thể đáp ứng các nhu cầu cơ bản.
Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.
Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:
Chào thân ái và quyết thắng!