Tiếng Việt

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:

Tạo giá trị BigInt

Có hai cách chính để tạo giá trị BigInt trong JavaScript:

  1. 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.
  2. 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ị BigIntNumber 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:

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 ý:

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ị BigIntNumber.

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 BigIntNumber 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:

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

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

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.