Cách gỡ rối những lối thường gặp trong Java [3] – Phép Chia Dài

gttn_500_01

      Bài viết này sẽ đề cập đến một chương trình chia hai giá trị long. Số bị chia tượng trưng cho số micro giây trong một ngày; số chia tượng trưng cho số milli giây trong một ngày. Theo bạn đoạn chương trình dưới đây sẽ in ra nội dung gì?

public class LongDivision
{
    public static void main(String[] args) {
        final long MICRO_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
        final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
        System.out.println(MICRO_PER_DAY/MILLIS_PER_DAY);
    }
}

–   Thoạt nhìn sơ qua bạn cũng biết đáp án của bài toán này có đáp số là 1000. Cả số chia và số bị chia có kiểu long, đủ lớn để chứa một trong hai tích mà không bị tràn. Vậy thì chương trình lẽ ra phải in 1000. Thật không may nó lại in ra 5. Chính xác điều gì đã xãy ra ở đây?

–   Vấn đề là phép tính của hằng MICRO_PER_DAY tràn. Mặc dù kế quả của phép tính nằm vừa trong một biến kiểu long nhưng nó lại không nằm vừa trong một biến kiểu int. Phép tính được thực hiện hoàn toàn trong số học và chỉ sau khi phép tính hoàn tất kết quả được tăng cấp lên thành một long. Nhưng lúc đó đã quá trễ: phép tính đã tràn, trả về một giá trị quá thấp với một thừa số là 200. Sự tăng cấp từ int lên thành long là một sự chuyển đổi nguyên thủy mở rộng duy trì giá trị số (không chính xác). Sau đó giá trị này được chia bởi MILLIS_PER_DAY, phép tính này đơợc tính chính xác bởi nó đã nằm vừa trong một biến int. Kết quả của phép chia này là 5.

–   Vậy  thì tại sao phép tính được thực hiện trong số học int? Bởi vì tất cả thừa số được nhân lại với nhau là những giá trị int. Khi bạn nhân hai giá trị int, bạn có được một giá trị int khác. Java không có sự định kiểu đích (Target typing), một tính năng ngôn ngữ mà loại biến là một kết quả sẽ được lưu trữ trong đó ảnh hưởng đến loại phép tính.

–   Chúng ta có thể sửa chương trình trên một cách dễ dàng bằng bằng cách sử dụng một trực kiện long thay cho một int làm thừa số đầu tiên trong mỗi tích. Điều này buộc tất cả các phép tính tiếp theo trong biểu thức được thực hiện với kiểu long. Mặc dù lần thực hiện điều này chỉ trong biểu thức cho MICROS_PER_DAY, nhưng nên thực hiện điều đó trong cả hai tích. Tương tự, không cần thiết phải luôn sử dụng một long làm giá trị đầu tiên trong một tích, nhưng nên làm như vậy. Việc bắt đầu cả hai phép tính với các giá trị long giúp làm rõ ràng rằng chúng sẽ không tràn. Chương trình dưới đây sẽ in ra đáp án 1000 như dự tính:

// thanhcuong.wordpress.com
public class LongDivision
{
    public static void main(String[] args) {
        final long MICRO_PER_DAY = 24L * 60 * 60 * 1000 * 1000;
        final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;
        System.out.println(MICRO_PER_DAY/MILLIS_PER_DAY);
    }
}

Bài học cho trường hợp này đơn giản là: Khi làm việc với các số lớn, hãy lưu ý đến sự tràn – nó là một kẻ giết người thầm lặng. Việc một biến đủ lớn để chứa một kết quả không có nghĩa rằng phép tính dẫn đến kết quả có kiểu chính xác. Khi nghi ngờ, hãy thực hiện toàn bộ phép tính bằng cách sử dụng số học long.

Bài học cho những nhà thiết kế ngôn ngữ là nên giảm đi khả năng tràn thầm lặng. Điều này có thể thực hiện bằng cách cung cấp sự hỗ trợ cho số học vốn không tràn một cách thầm lặng. Các chương trình có thể đưa ra một ngoại lệ (exception) thay vì làm tràn giống  như ngôn ngữ Ada, hoặc chúng có thể tự động chuyển sang một dạng biểu diễn trong lớn hơn khi được yêu cầu để tránh sự tràn giống như ngôn ngữ Lisp. Cả hai phương pháp có thể có những bất lợi thực thi đi cùng với chúng. Một cách khác để giảm khả năng tràn thầm lặng là hỗ trợ định kiểu đích (target typing), nhưng điều này tăng thêm sự phức tạp cho hệ thống kiểu.

Advertisements

About thanhcuong1990

Handsome and talent!! ^^
This entry was posted in Java. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s