Menu

Số trong R: numeric, integer và các hàm toán học

Làm việc với số trong R: numeric và integer, nhóm hàm làm tròn, sqrt và log, toán tử chia lấy dư %%, cùng các giá trị đặc biệt Inf và NaN.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Numeric và integer: hai kiểu số của R

R lưu số theo hai cách. Numeric (lưu dưới dạng số thực dấu phẩy động độ chính xác kép) là mặc định: mọi số bạn gõ đều là double trừ khi bạn nói khác. Integer là một kiểu số nguyên chính xác riêng biệt mà bạn yêu cầu bằng hậu tố L:

Trong thực tế bạn hiếm khi phải bận tâm: R tự chuyển đổi giữa hai kiểu, và is.numeric() là TRUE với cả hai (xem kiểu dữ liệu để biết toàn bộ hệ thống kiểu). Chỗ duy nhất khác biệt lộ ra là phép chia: / luôn trả về double, kể cả giữa hai số integer:

Integer chủ yếu xuất hiện dưới dạng kết quả của các hàm đếm (length(), seq_len(), nrow()) và giá trị chỉ số. Khi cần chia lấy phần nguyên, R có một toán tử riêng, được trình bày bên dưới.

Nhóm hàm làm tròn

R cho bạn năm cách cắt gọn một số, mỗi cách mang một ý nghĩa riêng:

  • round(x, digits): làm tròn đến một số chữ số thập phân (mặc định là 0).
  • floor(x): làm tròn xuống số nguyên gần nhất, luôn về phía âm vô cùng.
  • ceiling(x): làm tròn lên, luôn về phía dương vô cùng.
  • trunc(x): cắt bỏ phần thập phân, luôn về phía 0. Để ý khác biệt với số âm: trunc(-2.7) là -2, còn floor(-2.7) là -3.
  • signif(x, digits): làm tròn đến một số chữ số có nghĩa, không phải chữ số thập phân: signif(123456, 2) là 120000.

Và một điều bất ngờ nổi tiếng: round() dùng quy tắc làm tròn nửa về số chẵn (banker's rounding) với các giá trị đúng một nửa, theo chuẩn IEEE 754:

Kết quả in ra là 0 2 2 4: mỗi giá trị một nửa được làm tròn về số chẵn gần nhất. Đây không phải bug; nó ngăn sai lệch hướng lên có hệ thống khi bạn cộng nhiều giá trị đã làm tròn. Nếu một báo cáo cần cách làm tròn như ở trường, hãy cộng thêm một lượng rất nhỏ hoặc định dạng ở tầng hiển thị.

Các hàm toán học thường dùng

Những hàm cơ bản hoạt động đúng như bạn đoán:

sqrt() là căn bậc hai, abs() là giá trị tuyệt đối, ^ là lũy thừa, và exp(x) là e mũ x, nên exp(1) là số Euler, khoảng 2.718282.

Hàm khiến nhiều người vấp là log(). Trong R, log() là logarit tự nhiên (cơ số e), không phải cơ số 10:

Dòng đầu tiên in ra khoảng 4.60517, không phải 2 như người quen cơ số 10 mong đợi. Hãy dùng log10() và log2() khi bạn muốn các cơ số đó, hoặc truyền base = một cách tường minh. (Quy ước này là chuẩn trong thống kê, nơi logarit tự nhiên là mặc định.)

Chia lấy dư %% và chia lấy phần nguyên %/%

Hai toán tử lo phần số học về số dư:

%% là phép chia lấy dư (modulo): 17 chia 5 được 3 dư 2. %/% là phép chia lấy phần nguyên: có bao nhiêu lần 5 nằm trọn trong 17. Hai phép này cùng thỏa mãn x == (x %/% y) * y + (x %% y).

Cách dùng kinh điển của %% là kiểm tra tính chia hết:

Một điểm tinh tế với số âm: %% của R lấy dấu của số chia (giống Python, khác C):

Kết quả là 2, không phải -1: R trả lời câu hỏi "cần cộng bao nhiêu vào một bội số của 3 để được -7?", nhờ vậy kết quả luôn nằm trong 0..2 khi số chia dương. Rất tiện để quay vòng chỉ số; nhưng gây bất ngờ nếu bạn quen C hoặc Java.

Giá trị đặc biệt: Inf, -Inf và NaN

Số trong R tuân theo chuẩn IEEE 754, nên một số phép toán tạo ra giá trị đặc biệt thay vì báo lỗi:

1/0 là Inf (vô cùng), -1/0 là -Inf, còn 0/0, một đại lượng thực sự không xác định, là NaN, "not a number" (không phải số). Khác biệt này quan trọng: Inf là một đáp án ("lớn hơn mọi thứ"), còn NaN là sự vắng mặt của đáp án. Bạn kiểm tra chúng bằng các hàm riêng, vì == NaN không bao giờ hoạt động:

Để ý dòng cuối: NaN cũng được tính là NA, nên is.na() bắt được nó; thêm một lý do để is.na() là cách kiểm tra chuẩn cho câu hỏi "giá trị này có dùng được không?" (xem thêm ở giá trị thiếu).

R cũng đọc và ghi ký hiệu khoa học sẵn có: 2.5e3 là 2500, và các số rất nhỏ hoặc rất lớn mặc định được in ở dạng e:

Dùng format(x, scientific = FALSE) (hoặc tùy chọn scipen) khi báo cáo cần số thập phân thông thường.

Bất ngờ của số thực dấu phẩy động

Mọi ngôn ngữ lưu số thập phân ở dạng nhị phân đều gặp chuyện này, và R cũng không ngoại lệ:

Kết quả là FALSE, vì 0.1 + 0.2 thực ra là 0.30000000000000004. Cả 0.1 lẫn 0.2 đều không có biểu diễn nhị phân chính xác, và các sai số nhỏ cộng dồn lại. Cách in mặc định của R che giấu điều này bằng cách chỉ hiện 7 chữ số có nghĩa, đó là lý do vấn đề có vẻ vô hình cho đến khi một phép so sánh == thất bại.

Quy tắc: đừng bao giờ so sánh số thập phân đã tính toán bằng ==. Hãy dùng all.equal(), hàm so sánh trong một mức sai số hợp lý:

Bọc nó trong isTRUE() vì khi hai giá trị khác nhau, all.equal() trả về một mô tả về sự khác biệt (không phải FALSE). Với các phép tính số nguyên cần chính xác tuyệt đối, integer chính xác đến khoảng 2,1 tỷ, thêm một lý do để code đếm dùng kiểu integer.

Những điều cần nhớ

  • Mọi số bạn gõ đều là double; 42L tạo ra integer, và / luôn trả về double.
  • round() làm tròn nửa về số chẵn; floor/ceiling/trunc/signif mỗi hàm cắt gọn một kiểu, hãy biết mình cần hàm nào.
  • log() là logarit tự nhiên; dùng log10(), log2() hoặc base = cho các cơ số khác.
  • %% cho số dư (dấu theo số chia), %/% cho thương nguyên.
  • 1/0 là Inf, 0/0 là NaN, và 0.1 + 0.2 != 0.3: so sánh số thập phân bằng all.equal(), đừng bao giờ dùng ==.

Tiếp theo: nửa còn lại của dữ liệu hằng ngày, chuỗi (string) và những hàm R cung cấp để tạo, định dạng và tìm kiếm trong chuỗi.

Câu hỏi thường gặp

numeric và integer trong R khác nhau thế nào?

Numeric (double) là kiểu mặc định của R cho mọi số bạn gõ: 42 là double dù trông như số nguyên. Integer là một kiểu lưu trữ riêng mà bạn yêu cầu bằng hậu tố L: 42L. Phép chia thông thường luôn trả về double, kể cả giữa hai số integer; hãy dùng %/% để chia lấy phần nguyên.

log() trong R có phải là logarit tự nhiên không?

Đúng vậy: log(x) trong R là logarit tự nhiên (cơ số e), không phải cơ số 10. Dùng log10() cho cơ số 10, log2() cho cơ số 2, hoặc log(x, base = b) cho cơ số bất kỳ. log(100) xấp xỉ 4.605, không phải 2.

round() trong R hoạt động thế nào?

round(x, digits) làm tròn đến số chữ số thập phân cho trước, nhưng với các giá trị đúng một nửa thì dùng quy tắc "làm tròn nửa về số chẵn" (banker's rounding): round(2.5) là 2 và round(3.5) là 4. Cách này theo chuẩn IEEE 754 và giảm sai lệch khi cộng nhiều giá trị đã làm tròn, nhưng khiến những ai quen cách làm tròn ở trường bất ngờ.

Tại sao 0.1 + 0.2 không bằng 0.3 trong R?

Double được lưu ở dạng nhị phân, và 0.1, 0.2, 0.3 đều không có biểu diễn nhị phân chính xác, nên 0.1 + 0.2 thực ra là 0.30000000000000004. Đừng bao giờ so sánh số thập phân đã tính toán bằng ==; hãy dùng isTRUE(all.equal(x, y)) hoặc kiểm tra abs(x - y) < 1e-9.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu