Hướng dẫn toàn diện về BigInt trong JavaScript, bao gồm các tính năng và ứng dụng trong số học số nguyên lớn, giúp vượt qua giới hạn và tính toán chính xác.
JavaScript BigInt: Làm chủ Số học Số nguyên Lớn
JavaScript, mặc dù là một ngôn ngữ linh hoạt, nhưng lại có những hạn chế khi xử lý các số nguyên rất lớn. Kiểu `Number` tiêu chuẩn chỉ có thể biểu diễn chính xác các số nguyên đến một giới hạn nhất định, được gọi là `Number.MAX_SAFE_INTEGER`. Vượt quá giới hạn này, các phép tính trở nên không chính xác, dẫn đến kết quả không mong muốn. Đây là lúc BigInt
ra tay giải cứu. Được giới thiệu trong ECMAScript 2020, BigInt
là một đối tượng tích hợp sẵn cung cấp cách biểu diễn và thao tác với các số nguyên có kích thước tùy ý, vượt qua giới hạn của kiểu `Number` tiêu chuẩn.
Hiểu về sự cần thiết của BigInt
Trước khi có BigInt
, các nhà phát triển JavaScript phải dựa vào các thư viện hoặc các triển khai tùy chỉnh để xử lý các phép tính số nguyên lớn. Các giải pháp này thường đi kèm với chi phí hiệu năng và tăng độ phức tạp. Sự ra đời của BigInt
đã cung cấp một cách tự nhiên và hiệu quả để làm việc với các số nguyên lớn, mở ra khả năng cho các ứng dụng trong nhiều lĩnh vực khác nhau, bao gồm:
- Mật mã học: Xử lý an toàn các số nguyên tố lớn là rất quan trọng đối với các thuật toán mật mã.
- Tính toán tài chính: Biểu diễn chính xác các giá trị tiền tệ lớn mà không làm mất độ chính xác.
- Tính toán khoa học: Thực hiện các phép tính phức tạp liên quan đến các số cực lớn hoặc cực nhỏ.
- Dấu thời gian có độ chính xác cao: Biểu diễn dấu thời gian với độ chính xác nano giây.
- Tạo ID: Tạo các mã định danh duy nhất và rất lớn.
Tạo giá trị BigInt
Có hai cách chính để tạo giá trị BigInt
trong JavaScript:
- Sử dụng hàm khởi tạo `BigInt()`: Hàm khởi tạo này có thể chuyển đổi một số, chuỗi hoặc giá trị boolean thành một
BigInt
. - Sử dụng hậu tố `n`: Thêm `n` vào cuối một số nguyên dạng chữ (literal) sẽ tạo ra một
BigInt
.
Ví dụ:
Sử dụng hàm khởi tạo `BigInt()`:
const bigIntFromNumber = BigInt(12345678901234567890);
const bigIntFromString = BigInt("98765432109876543210");
const bigIntFromBoolean = BigInt(true); // Kết quả là 1n
const bigIntFromFalseBoolean = BigInt(false); // Kết quả là 0n
console.log(bigIntFromNumber); // Đầu ra: 12345678901234567890n
console.log(bigIntFromString); // Đầu ra: 98765432109876543210n
console.log(bigIntFromBoolean); // Đầu ra: 1n
console.log(bigIntFromFalseBoolean); // Đầu ra: 0n
Sử dụng hậu tố `n`:
const bigIntLiteral = 12345678901234567890n;
console.log(bigIntLiteral); // Đầu ra: 12345678901234567890n
Lưu ý quan trọng: Bạn không thể trực tiếp trộn lẫn các giá trị BigInt
và Number
trong các phép toán số học. Bạn cần chuyển đổi chúng một cách rõ ràng về cùng một kiểu trước khi thực hiện các phép tính. Việc cố gắng trộn lẫn chúng trực tiếp sẽ dẫn đến lỗi `TypeError`.
Các phép toán số học với BigInt
BigInt
hỗ trợ hầu hết các toán tử số học tiêu chuẩn, bao gồm:
- Phép cộng (`+`)
- Phép trừ (`-`)
- Phép nhân (`*`)
- Phép chia (`/`)
- Phép chia lấy dư (`%`)
- Phép lũy thừa (`**`)
Ví dụ:
const a = 12345678901234567890n;
const b = 98765432109876543210n;
const sum = a + b;
const difference = a - b;
const product = a * b;
const quotient = a / 2n; // Lưu ý: Phép chia sẽ cắt bỏ phần thập phân (làm tròn về 0)
const remainder = a % 7n;
const power = a ** 3n; // Phép lũy thừa hoạt động như mong đợi
console.log("Sum:", sum); // Đầu ra: Sum: 111111111011111111100n
console.log("Difference:", difference); // Đầu ra: Difference: -86419753208641975320n
console.log("Product:", product); // Đầu ra: Product: 1219326311370217957951669538098765432100n
console.log("Quotient:", quotient); // Đầu ra: Quotient: 6172839450617283945n
console.log("Remainder:", remainder); // Đầu ra: Remainder: 5n
console.log("Power:", power); // Đầu ra: Power: 1876412810291823000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n
Những điểm cần lưu ý:
- Phép chia: Phép chia với các giá trị
BigInt
sẽ làm tròn về phía số không. Điều này có nghĩa là phần thập phân của kết quả sẽ bị loại bỏ. Nếu bạn cần phép chia chính xác hơn, hãy xem xét sử dụng các thư viện hỗ trợ số học có độ chính xác tùy ý. - Toán tử cộng một ngôi (+): Toán tử cộng một ngôi (+) không thể được sử dụng với các giá trị
BigInt
vì nó sẽ xung đột với mã asm.js cũ. Hãy sử dụng hàm chuyển đổi `Number()` để chuyển đổi một BigInt thành một Number nếu bạn cần một biểu diễn số (với sự hiểu biết rằng bạn có thể mất độ chính xác). - Toán tử Bitwise:
BigInt
cũng hỗ trợ các toán tử bitwise như `&`, `|`, `^`, `~`, `<<`, và `>>`. Các toán tử này hoạt động như mong đợi trên biểu diễn nhị phân của các giá trịBigInt
.
Toán tử so sánh
Bạn có thể sử dụng các toán tử so sánh tiêu chuẩn (`==`, `!=`, `<`, `>`, `<=`, `>=`) để so sánh các giá trị BigInt
với các giá trị BigInt
khác hoặc thậm chí với các giá trị Number
. Tuy nhiên, hãy lưu ý về khả năng ép kiểu.
Ví dụ:
const a = 10n;
const b = 20n;
const c = 10;
console.log(a == b); // Đầu ra: false
console.log(a != b); // Đầu ra: true
console.log(a < b); // Đầu ra: true
console.log(a > b); // Đầu ra: false
console.log(a <= b); // Đầu ra: true
console.log(a >= b); // Đầu ra: false
console.log(a == c); // Đầu ra: true (ép kiểu)
console.log(a === c); // Đầu ra: false (không ép kiểu)
Thực hành tốt nhất: Sử dụng toán tử so sánh bằng nghiêm ngặt (`===`) và không bằng nghiêm ngặt (`!==`) để tránh việc ép kiểu không mong muốn khi so sánh các giá trị BigInt
và Number
.
Chuyển đổi giữa BigInt và Number
Mặc dù các phép toán số học trực tiếp giữa BigInt
và Number
không được phép, bạn có thể chuyển đổi giữa hai kiểu này. Tuy nhiên, hãy lưu ý về khả năng mất độ chính xác khi chuyển đổi một BigInt
thành một Number
nếu giá trị BigInt
vượt quá `Number.MAX_SAFE_INTEGER`.
Ví dụ:
const bigIntValue = 9007199254740991n; // Number.MAX_SAFE_INTEGER
const numberValue = Number(bigIntValue); // Chuyển đổi BigInt sang Number
console.log(numberValue); // Đầu ra: 9007199254740991
const largerBigIntValue = 9007199254740992n; // Vượt quá Number.MAX_SAFE_INTEGER
const largerNumberValue = Number(largerBigIntValue);
console.log(largerNumberValue); // Đầu ra: 9007199254740992 (có thể không chính xác)
const numberToBigInt = BigInt(12345); // Chuyển đổi Number sang BigInt
console.log(numberToBigInt); // Đầu ra: 12345n
Các trường hợp sử dụng và ví dụ
Mật mã học
Các thuật toán mật mã thường dựa vào các số nguyên tố rất lớn để đảm bảo an ninh. BigInt
cung cấp một cách để biểu diễn và thao tác hiệu quả với những con số này.
// Ví dụ: Tạo một cặp khóa đơn giản (không an toàn)
function generateKeyPair() {
const p = 281n; // Một số nguyên tố
const q = 283n; // Một số nguyên tố khác
const n = p * q; // Modulus
const totient = (p - 1n) * (q - 1n); // Hàm phi Euler
// Chọn một e (số mũ công khai) sao cho 1 < e < totient và gcd(e, totient) = 1
const e = 17n;
// Tính d (số mũ bí mật) sao cho (d * e) % totient = 1
let d = 0n;
for (let i = 1n; i < totient; i++) {
if ((i * e) % totient === 1n) {
d = i;
break;
}
}
return {
publicKey: { n, e },
privateKey: { n, d },
};
}
const keyPair = generateKeyPair();
console.log("Public Key:", keyPair.publicKey);
console.log("Private Key:", keyPair.privateKey);
Lưu ý: Đây là một ví dụ đơn giản hóa chỉ nhằm mục đích minh họa. Mật mã học trong thế giới thực sử dụng các số nguyên tố lớn hơn nhiều và các thuật toán phức tạp hơn.
Tính toán tài chính
Khi xử lý các khoản tiền lớn, đặc biệt là trong các giao dịch quốc tế, độ chính xác là rất quan trọng. BigInt
có thể ngăn ngừa lỗi làm tròn và đảm bảo các phép tính chính xác.
// Ví dụ: Tính lãi kép
function calculateCompoundInterest(principal, rate, time) {
const principalBigInt = BigInt(principal * 100); // Chuyển đổi sang cent
const rateBigInt = BigInt(rate * 10000); // Chuyển thành một phần mười nghìn của một phần trăm
const timeBigInt = BigInt(time);
let amount = principalBigInt;
for (let i = 0n; i < timeBigInt; i++) {
amount = amount * (10000n + rateBigInt) / 10000n;
}
const amountInDollars = Number(amount) / 100;
return amountInDollars;
}
const principal = 1000000; // $1,000,000
const rate = 0.05; // Lãi suất 5%
const time = 10; // 10 năm
const finalAmount = calculateCompoundInterest(principal, rate, time);
console.log("Final Amount:", finalAmount); // Đầu ra: Final Amount: 1628894.6267774413 (xấp xỉ)
Trong ví dụ này, chúng tôi chuyển đổi tiền gốc và lãi suất thành các giá trị BigInt
để tránh lỗi làm tròn trong quá trình tính toán. Kết quả sau đó được chuyển đổi trở lại thành một Number
để hiển thị.
Làm việc với ID lớn
Trong các hệ thống phân tán, việc tạo ID duy nhất trên nhiều máy chủ có thể là một thách thức. Sử dụng BigInt
cho phép bạn tạo các ID rất lớn mà khả năng trùng lặp là rất thấp.
// Ví dụ: Tạo ID duy nhất dựa trên dấu thời gian và ID máy chủ
function generateUniqueId(serverId) {
const timestamp = BigInt(Date.now());
const serverIdBigInt = BigInt(serverId);
const random = BigInt(Math.floor(Math.random() * 1000)); // Thêm một chút ngẫu nhiên
// Kết hợp các giá trị để tạo ID duy nhất
const uniqueId = (timestamp << 20n) + (serverIdBigInt << 10n) + random;
return uniqueId.toString(); // Trả về dưới dạng chuỗi để xử lý dễ dàng
}
const serverId = 123; // ID máy chủ ví dụ
const id1 = generateUniqueId(serverId);
const id2 = generateUniqueId(serverId);
console.log("Unique ID 1:", id1);
console.log("Unique ID 2:", id2);
BigInt và JSON
Các giá trị BigInt
không được JSON hỗ trợ nguyên bản. Việc cố gắng tuần tự hóa một đối tượng JavaScript chứa BigInt
bằng `JSON.stringify()` sẽ dẫn đến lỗi `TypeError`. Để xử lý các giá trị BigInt
khi làm việc với JSON, bạn có một vài lựa chọn:
- Chuyển thành chuỗi: Chuyển
BigInt
thành chuỗi trước khi tuần tự hóa. Đây là cách tiếp cận phổ biến và đơn giản nhất. - Tuần tự hóa/Giải tuần tự hóa tùy chỉnh: Sử dụng một hàm tuần tự hóa/giải tuần tự hóa tùy chỉnh để xử lý các giá trị
BigInt
.
Ví dụ:
Chuyển thành chuỗi:
const data = {
id: 12345678901234567890n,
name: "Example Data",
};
// Chuyển BigInt sang chuỗi trước khi tuần tự hóa
data.id = data.id.toString();
const jsonData = JSON.stringify(data);
console.log(jsonData); // Đầu ra: {"id":"12345678901234567890","name":"Example Data"}
// Khi giải tuần tự hóa, bạn cần chuyển chuỗi trở lại thành BigInt
const parsedData = JSON.parse(jsonData, (key, value) => {
if (key === "id") {
return BigInt(value);
}
return value;
});
console.log(parsedData.id); // Đầu ra: 12345678901234567890n
Tuần tự hóa/Giải tuần tự hóa tùy chỉnh (sử dụng `replacer` và `reviver`):
const data = {
id: 12345678901234567890n,
name: "Example Data",
};
// Tuần tự hóa tùy chỉnh
const jsonData = JSON.stringify(data, (key, value) => {
if (typeof value === 'bigint') {
return value.toString();
} else {
return value;
}
});
console.log(jsonData);
// Giải tuần tự hóa tùy chỉnh
const parsedData = JSON.parse(jsonData, (key, value) => {
if (typeof value === 'string' && /^[0-9]+$/.test(value)) { //kiểm tra xem nó có phải là một số và là một chuỗi không
try {
return BigInt(value);
} catch(e) {
return value;
}
}
return value;
});
console.log(parsedData.id);
Khả năng tương thích với trình duyệt
BigInt
được hỗ trợ rộng rãi trong các trình duyệt hiện đại. Tuy nhiên, điều cần thiết là phải kiểm tra khả năng tương thích với các trình duyệt hoặc môi trường cũ hơn. Bạn có thể sử dụng một công cụ như Can I use để xác minh hỗ trợ của trình duyệt. Nếu bạn cần hỗ trợ các trình duyệt cũ, bạn có thể xem xét sử dụng polyfill, nhưng hãy lưu ý rằng polyfill có thể ảnh hưởng đến hiệu năng.
Những cân nhắc về hiệu năng
Mặc dù BigInt
cung cấp một cách mạnh mẽ để làm việc với các số nguyên lớn, điều quan trọng là phải nhận thức được những tác động tiềm tàng về hiệu năng.
- Các phép toán
BigInt
có thể chậm hơn so với các phép toánNumber
tiêu chuẩn. - Việc chuyển đổi giữa
BigInt
vàNumber
cũng có thể gây ra chi phí hiệu năng.
Do đó, chỉ sử dụng BigInt
khi cần thiết, và tối ưu hóa mã của bạn để đạt hiệu năng tốt nếu bạn đang thực hiện một số lượng lớn các phép toán BigInt
.
Kết luận
BigInt
là một sự bổ sung có giá trị cho JavaScript, cho phép các nhà phát triển xử lý số học số nguyên lớn với độ chính xác cao. Bằng cách hiểu các tính năng, hạn chế và các trường hợp sử dụng của nó, bạn có thể tận dụng BigInt
để xây dựng các ứng dụng mạnh mẽ và chính xác trong nhiều lĩnh vực khác nhau, bao gồm mật mã học, tính toán tài chính và tính toán khoa học. Hãy nhớ xem xét khả năng tương thích của trình duyệt và các tác động về hiệu năng khi sử dụng BigInt
trong các dự án của bạn.
Tìm hiểu thêm
- Mozilla Developer Network (MDN) - BigInt
- V8 Blog - BigInt: Arbitrary-Precision Integers in JavaScript
Hướng dẫn này cung cấp một cái nhìn tổng quan toàn diện về BigInt
trong JavaScript. Hãy khám phá các tài nguyên được liên kết để có thêm thông tin chuyên sâu và các kỹ thuật nâng cao.