Thuật toán Lexicographical Comparison: Vì sao '20' > '3' lại trả về false trong JavaScript?

Khi xử lý dữ liệu từ file CSV, Input Form hay API, chúng ta thường gặp tình trạng so sánh hai con số nhưng kết quả lại hoàn toàn vô lý (ví dụ '20' lại nhỏ hơn '3'). Bài viết này sẽ mổ xẻ thuật toán Lexicographical Comparison - thủ phạm đứng sau những bug ngớ ngẩn này và cách phòng tránh triệt để.

28/05/2026
5 phút đọc đọc
0 lượt xem
1

Ảo giác của những con số

Trong quá trình code JavaScript hoặc TypeScript, không ít lần bạn lấy giá trị từ một input HTML (ví dụ <input type="number" />) hoặc một field từ file CSV và đem đi so sánh:

const a = "20";
const b = "3";

if (a > b) {
  console.log("A lớn hơn B");
} else {
  console.log("A nhỏ hơn B");
}

Về mặt toán học, 20 lớn hơn 3 là điều hiển nhiên. Nhưng nếu chạy đoạn code trên, kết quả in ra sẽ là A nhỏ hơn B ('20' > '3' trả về false).

Lý do là vì ab đang có kiểu dữ liệu là chuỗi (String). Khi so sánh hai chuỗi với nhau bằng toán tử > hoặc <, JavaScript KHÔNG quan tâm bên trong chuỗi chứa gì (chữ cái hay con số), nó chỉ áp dụng một thuật toán duy nhất: Lexicographical Comparison (So sánh theo từ điển).

2

Thuật toán Lexicographical hoạt động ra sao?

Thuật toán Lexicographical (hay còn gọi là Alphabetical order) so sánh hai chuỗi giống hệt như cách bạn tra từ điển. Nó duyệt qua từng ký tự từ trái sang phải của cả hai chuỗi và so sánh mã Unicode (hoặc giá trị UTF-16) của chúng.

Các bước cụ thể của thuật toán trong V8 Engine (trình duyệt Chrome/Node.js):

  1. Lấy ký tự đầu tiên của chuỗi A và ký tự đầu tiên của chuỗi B.
  2. So sánh giá trị số của hai ký tự này.
  3. Nếu bằng nhau, chuyển sang cặp ký tự tiếp theo ở vị trí thứ 2, thứ 3...
  4. Nếu khác nhau, ký tự nào có giá trị lớn hơn thì chuỗi chứa ký tự đó được coi là LỚN HƠN, và thuật toán dừng lại ngay lập tức. Nó tuyệt đối bỏ qua và không cần quan tâm các ký tự phía sau dài hay ngắn cỡ nào.
3

Breakdown ví dụ '20' và '3'

Bây giờ chúng ta áp dụng thuật toán trên vào việc so sánh '20''3':

  • Ký tự đầu tiên của chuỗi thứ nhất là '2' (Mã Unicode: 50).
  • Ký tự đầu tiên của chuỗi thứ hai là '3' (Mã Unicode: 51).
  • 50 < 51 (tức là '2' < '3'), thuật toán đưa ra kết luận ngay: Chuỗi '20' nhỏ hơn chuỗi '3'.
Cái bẫy chết người: Số 0 phía sau số 2 hoàn toàn vô nghĩa và không bao giờ được engine kiểm tra đến. JavaScript đã "chốt đơn" ngay từ ký tự đầu tiên. Đây chính là bug logic cực kỳ phổ biến đối với các bạn mới học lập trình hoặc khi vội vã parse CSV data mà quên xử lý kiểu.
4

Quy tắc "Tiền tố" (Prefix rule)

Có một trường hợp thú vị khác: Điều gì xảy ra nếu bạn so sánh '2''20'?

console.log('2' > '20'); // Trả về false ('2' < '20')

Quá trình duyệt sẽ diễn ra như sau:

  1. So sánh ký tự đầu: '2''2' bằng nhau. Tiếp tục sang ký tự thứ 2.
  2. So sánh ký tự thứ hai: Chuỗi trái đã hết ký tự (hết độ dài), chuỗi phải là '0'.

Theo nguyên tắc của thuật toán Lexicographical, nếu một chuỗi là "tiền tố" (prefix) của chuỗi kia (tức chuỗi này giống hệt toàn bộ phần đầu của chuỗi kia), thì chuỗi nào ngắn hơn sẽ được quy ước là nhỏ hơn.

Do đó, '2' ngắn hơn nên nó nhỏ hơn '20'. Kết quả này vô tình trùng khớp với toán học (2 nhỏ hơn 20), nhưng bạn phải nhớ rằng bản chất bên dưới hệ thống vẫn là đang "so sánh chữ" (string comparison) chứ không hề có phép toán số học nào ở đây!

5

Cách khắc phục và phòng tránh

Cách duy nhất và chuẩn xác nhất để tránh những bug đau đầu này là luôn luôn ép kiểu (Type Coercion) dữ liệu về Number trước khi sử dụng các toán tử so sánh (>, <, >=, <=).

const a = "20";
const b = "3";

// Sử dụng Number(), hoặc dấu +
if (Number(a) > Number(b)) {
  console.log("Toán học đã trở lại!"); // Khối lệnh này sẽ được chạy
}

// Hoặc sử dụng parseInt / parseFloat khi lấy data từ file text/CSV
const age = parseInt(csvRecord.age, 10);
const limit = 18;

if (age > limit) {
  // Logic hợp lệ an toàn tuyệt đối
}
Best Practice: Trong TypeScript, hãy định nghĩa kiểu dữ liệu chặt chẽ cho các variable ngay từ đầu. Mặc dù trình biên dịch (compiler) của TS sẽ báo lỗi màu đỏ nếu bạn so sánh string > number, nhưng nó sẽ "vui vẻ cho qua" nếu bạn so sánh string > string. Do đó, duy trì sự cảnh giác với các giá trị string chứa số là kỹ năng sinh tồn của mọi Frontend và Node.js Engineer.

Bài viết liên quan

Bẻ Khóa TypeScript: Mapped Types & Conditional Types

Làm chủ các khái niệm nâng cao trong TypeScript để xây dựng các thư viện an toàn kiểu dữ liệu (type-safe) 100%.

14/05/2026
14 phút đọc