Kiểu dữ liệu NaN trong JavaScript - Một cái nhìn sâu sắc

Có lẽ rằng trong khi lập trình nói chung và nhất là với Javascript nói riêng đã không ít lần chúng ta gặp trường hợp một biến của chúng ta trả về một kiểu dữ liệu không mấy đẹp đẽ lắm là NaN. Những người chưa hiểu sâu về nó thường nghĩ ngay đến đã có gì đó sai sai trong xử lý mà dẫn đến kiểu dữ liệu này, hay đây là một loại thông báo lỗi mới, hay là do ăn ở... Tất nhiên chúng ta không thể trả lời một vấn đề kĩ thuật bằng những lý do rất "thiểu năng" như thế được. Chính vì thế nên chúng ta mới ngồi đây cùng nhau tìm hiểu rõ hơn. Rốt cuộc thì NaN nó là gì, khi nào nó xuất hiện và chúng ta sẽ xử lý nó như thế nào. OK bắt đầu thôi.

NaN là gì nhỉ?

Đầu tiên, NaN không phải là một từ khóa trong JavaScript giống như true, false, null ... mà nó là một thuộc tính của global object. Giá trị của NaN giống như giá trị của Number.NaN:

NaN; // NaN
Number.NaN; // NaN

Vậy chúng ta có thể thấy rằng NaN được xem như một kiểu Number. Lý do tại sao thì chúng ta sẽ cùng nhau thảo luận trong các phần tiếp theo của bài viết này. Để kiểm chứng điều đó chúng ta hoàn toàn có thể sử dụng từ khóa typeof. Cách đơn giản nhất là hãy bật ngay cửa sổ console của trình duyệt lên và thử gõ vào đoạn lệnh sau:

typeof NaN
'number'

OK, kết quả hiển thị cho thấy NaN được xem như một kiểu Number. Vậy NaN có thể xuất hiện trong các trường hợp nào. Hãy cùng nhau khám phá tiếp nhé

Các cách để phát sinh NaN

  • Lấy số 0 chia cho số 0
  • Lấy vô cùng (infinity) chia cho vô cùng (infinity)
  • Nhân vô cùng (infinity) với số 0
  • Bất kỳ phép tính toán nào trong đó NaN là một toán hạng
  • Chuyển đổi một xâu non-numeric hoặc undefined về dạng number

Tại sao kiểu dữ liệu của NaN là 'number'?

Như định nghĩa, NaN là giá trị trả về từ một biểu thức mà kết quả là số không được xác định (undefined numerical). Tức là nó vẫn là kiểu dữ liệu numeric, tuy nhiên nó chưa xác định như một kiểu số thực. Chính vì thế nên trong JavaScript, ngoài việc trở thành một thành phần trong global object, nó còn là một thành phần của Number object. Ngoài ra NaN cũng đại diện cho bất kì số nào thuộc ngoài khoảng biểu diễn của miền giá trị cho phép theo chuẩn ECMAScript. Bây giờ chúng ta sẽ cùng nhau thảo luận một số vấn đề liên quan đến NaN nhé.

Sự giới hạn của số học máy tính

Chúng ta cùng xem xét biểu thức sau:

 (3.2317006071311 * 10e616) / (3.2317006071311 * 10e616); // NaN

Biểu thức trên cho giá trị là NaN. Đẻ giải thích vấn đề này, Wikipedia có viết:

Computer arithmetic cannot directly operate on real numbers, but only on a finite subset of rational numbers, limited by the number of bits used to store them.

Theo như giải thích từ Wikipedia, vấn để ở chỗ máy tính không thể tính toán trực tiếp các giá trị của các số thực nếu vượt quá giới hạn cho phép, chính vì do giới hạn số lượng các bits sử dụng để lưu trữ các giá trị trong biểu thức đó. Chúng ta cùng nhau xem xét đến biểu thức trên. Theo thứ tự thực hiện của biểu thức. Đầu tiên nó sẽ tính toán giá trị của 3.2317006071311 * 10616 là một số thực. Tuy nhiên theo quy định của chuẩn ECMAScript thì giá trị của số này quá lớn, vượt quá giá trị của Number.MAX_VALUE và nó được biểu diễn dưới kiểu dữ liệu là vô cùng (Infinity). Tương tự biểu thức (3.2317006071311 * 10e616) cũng trả về dữ liệu dạng (Infinity). Theo như định nghĩa ở phần trên, kết quả của biểu thức sẽ trả về NaN. Trong trường hợp này, NaN được dùng để biểu diễn một giá trị không thể tính toán được bằng số học máy tính

NaN không có thứ tự

Theo như chuẩn IEEE 754, mọi phép so sánh với NaN luôn luôn trả về một kết quả là false. Điều này có nghĩa rằng, NaN không thể so sánh bằng, lớn hơn, hay nhỏ hơn bất kì số nào, kể cả chính nó. Chúng ta có thể xem xét các biểu thức sau để hiểu rõ hơn vấn đề:

NaN < 1;    // false
NaN > 1;    // false
NaN == NaN; // false
// But we can still check for NaN:
isNaN(NaN); // true

Vì vậy để so sánh một giá trị trả về của một biểu thức là NaN hay không chúng ta phải sử dụng hàm isNaN() thay vì sử dụng toán tử == thông thường. Chúng ta cũng có thể tự định nghĩa lại một hàm isNaN() cho riêng mình như sau:

// Viết lại hàm NaN
function isNaN(x) {
  // Ép kiểu Number cho biến x
  x = Number(x);
  // Nếu x là NaN, NaN != NaN trả về true, các trường hợp khác sẽ trả về false
  return x != x;
}

Đơn giản và dễ hiểu phải không nào.

Đừng nhầm lẫn Boolean và NaN

Chúng ta cùng thử xem xét đoạn code sau:

isNaN(true);  // false
isNaN(false); // false

Boolean là một kiểu dữ liệu sử dụng bit nhị phân để biểu diễn giá trị. Nếu chúng ta ép kiểu nó sang kiểu Number nó sẽ có kết quả như sau:

Number(true);  // 1
Number(false); // 0

Và rõ ràng nó là các kiểu Number xác định, hoàn toàn khác với khái niệm của NaN Tổng kết Qua bài viết, mình đã trình bày khái niệm và một số vấn đề cần thảo luận về NaN. Hi vọng rằng nó giúp các bạn hiểu hơn về kiểu dữ liệu này và có nhiều cách xử lý linh hoạt hơn trong các trường hợp phải làm việc với NaN trong Javascript bạn nhé.

All Rights Reserved