Trong JavaScript hiện đại, có hai loại số:

  1. 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.
  2. 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.

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:

// -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)

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: 0xvà 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ố 0b0o:

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).

toString (base)

Phương thức num.toString(base)trả về một chuỗi đại diện numtrong 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

basethể 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..9hoặc A..F.
  • base = 2 chủ yếu để gỡ lỗi các thao tác bitwise, các chữ số có thể 0hoặc 1.
  • cơ sở = 36 là tối đa, chữ số có thể 0..9hoặc A..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 ích 36là 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ư toStringtrong 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).

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.1trở thành 3, và -1.1trở thành -2.

Math.ceil

Làm tròn lên: 3.1trở thành 4, và -1.1trở thành -1.

Math.round




Làm tròn đến số nguyên gần nhất: 3.1trở thành 3, 3.6trở thành 4-1.1trở 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.1trở thành 3, -1.1trở thành -1.

Đây là bảng để tóm tắt sự khác biệt giữa chúng:

Math.floorMath.ceilMath.roundMath.trunc
3.13433
3.63443
-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-thchữ số sau số thập phân thì sao?

Chẳng hạn, chúng ta có 1.2345và 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:

  1. 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 nchữ 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ả toFixedlà 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
  1. 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).

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.10.20.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$0.20hà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.2trô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.1gì? 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 3thì 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/10trở 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.2khô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 toFixedluô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-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.

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ị NaNnà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ề truenế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ư 0trong 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:

  1. Nó hoạt động với NaN: Object.is(NaN, NaN) === true, đó là một điều tốt.
  2. Giá trị 0-0khá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).

parseInt và parseFloat

Chuyển đổi số bằng cách sử dụng một cộng +hoặc Number()là 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ì parseIntparseFloat 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 parseInttrả về một số nguyên, trong khi parseFloatsẽ 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/parseFloatsẽ 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 đó parseIntcũ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

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ề ntă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 Mathđối tượng, 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.

Tóm lược

Để viết số có nhiều số 0:

  • Nối "e"với số 0. Giống như: 123e6giống như 123vớ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-6nghĩa là 0.000123( 123phầ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ỗi strthành một số nguyên trong hệ thống số với base, 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ố đã cho base.

Để chuyển đổi các giá trị như 12pt100pxthành một số:




  • Sử dụng parseInt/parseFloatcho 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.roundhoặc num.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.