Một hàm gom tọa độ vào ô lưới chạy đúng với toàn bộ dữ liệu dương, rồi sai ngay khi người dùng kéo sang bên trái gốc tọa độ. Nguyên nhân có thể nằm ở việc dùng Math.trunc thay cho Math.floor. Cả hai trả về phần nguyên theo một quy tắc, nhưng chúng trả lời hai câu hỏi khác nhau.
Bài đối chiếu tài liệu ngày 08/11/2026 và chạy ví dụ bằng Node.js 24.18.1. Các tiêu chí so sánh là ý nghĩa phép tính, hành vi với số âm, kiểm soát đầu vào và khả năng đọc code. Bài không có benchmark và không kết luận API nào chạy nhanh hơn trên browser hoặc thiết bị di động.
Hai quy tắc trùng nhau trên số dương
Math.floor chọn số nguyên lớn nhất không vượt quá đầu vào. Math.trunc bỏ phần thập phân, tiến về phía 0. Vì thế, chỉ thử 2.7 sẽ không giúp phân biệt hai cách làm.
for (const x of [-2.7, -0.2, 0, 2.7]) {
console.log(x, Math.floor(x), Math.trunc(x));
}
console.log(Object.is(Math.trunc(-0.2), -0));
Với -2.7, kết quả là -3 và -2. Với -0.2, chúng là -1 và -0; phép kiểm tra cuối trả true. Hai dòng còn lại cho cặp 0, 0 và 2, 2. Trong bài toán chỉ cần bỏ phần lẻ của một độ lệch, trunc có thể phù hợp; khi đang chia mặt phẳng thành các khoảng liên tiếp, floor thường diễn đạt đúng hơn.
So sánh bằng một hợp đồng ô lưới cụ thể
Giả sử mỗi ô rộng 10 đơn vị, gốc ở 0 và ô k chứa các điểm từ k * 10 đến trước (k + 1) * 10. Theo hợp đồng này, tọa độ -1 thuộc ô -1, tọa độ 0 thuộc ô 0 và tọa độ 10 thuộc ô 1. Không có tranh luận về tên hàm ở đây: chỉ cần kiểm tra hàm có đáp ứng các khoảng đã định nghĩa hay không.
function cellIndex(x, width) {
if (!Number.isFinite(x) || !Number.isFinite(width) || width <= 0) {
throw new RangeError('Expected finite x and positive finite width');
}
const ratio = x / width;
if (!Number.isFinite(ratio)) throw new RangeError('Ratio overflow');
const result = Math.floor(ratio);
return result === 0 ? 0 : result;
}
for (const x of [-11, -10, -1, 0, 9, 10]) {
console.log(x, cellIndex(x, 10));
}
Các chỉ số lần lượt là -2, -1, -1, 0, 0, 1. Thay floor bằng trunc sẽ đưa -1 về ô 0, trái với hợp đồng. Ví dụ chủ động chuẩn hóa -0 thành 0 ở kết quả vì không dùng dấu của số 0 để phân biệt ô.
Đây là minh họa số học, chưa nối vào canvas, bản đồ hay hệ tọa độ có phép biến đổi. Nếu tọa độ đi qua nhiều phép tính số thực, cần kiểm tra thêm điểm sát ranh giới; ví dụ này không tự giải quyết mọi sai số tích lũy.
Chi phí áp dụng và quyền kiểm soát
Cả hai API đều có sẵn trong JavaScript, nên ví dụ không thêm dependency hoặc cấu hình build. Phần công việc thực tế nằm ở xác định quy tắc: làm tròn xuống hay bỏ phần lẻ? Đặt tên helper theo nghiệp vụ như cellIndex giúp người review kiểm tra quy tắc ấy mà không phải đoán mục đích từ một phép chia.
Không dùng lựa chọn này để thay thế validation. Hai hàm vẫn có hành vi với NaN và vô cực; helper ở trên từ chối chúng vì một chỉ số ô hữu ích phải xuất phát từ đầu vào hữu hạn. Chuỗi '10' cũng bị từ chối thay vì được tự động coi như số.
Tránh mẹo bitwise khi chỉ cần bỏ phần lẻ
Một biểu thức như x | 0 có thể trông ngắn hơn trunc nhưng kéo theo phép chuyển về số nguyên 32 bit. Hãy thử:
console.log(Math.trunc(4294967296.5));
console.log(4294967296.5 | 0);
Hai kết quả là 4294967296 và 0. Đây là khác biệt về giá trị, không phải một tối ưu có thể áp dụng mù. Chọn một hàm có tên mô tả quy tắc giúp tránh thêm một giới hạn miền dữ liệu ngoài dự kiến.
Cách chọn và kiểm tra
Chọn floor khi hợp đồng cần cận nguyên phía dưới, như các khoảng ô lưới vừa nêu. Chọn trunc khi hợp đồng yêu cầu giữ phần nguyên về phía 0. Trong cả hai trường hợp, thêm ít nhất một số âm có phần lẻ, một điểm biên nguyên và một đầu vào không hợp lệ vào ca kiểm tra.
Đừng dùng kết quả trùng nhau trên số dương làm bằng chứng rằng hai API thay thế được nhau. Viết quy tắc đầu ra trước sẽ giúp việc chọn API trở thành bước trực tiếp, dễ kiểm tra.

