2012-10-26 38 views
7

Tôi muốn hiểu cách kiểu Java double sẽ lưu trữ giá trị của nó trong bộ nhớ trong Java. Khi tôi chạy đoạn mã sau tôi nhận được kết quả bất ngờ:Hành vi bất ngờ của dữ liệu kiểu nguyên thủy kép

public static void main(String[] args) { 

    float a = 1.5f; 
    float b= 0.5f; 
    double c= 1.5; 
    double d = 0.5; 

    float a1 = 1.4f; 
    float b1= 0.5f; 
    double c1= 1.4; 
    double d1 = 0.5; 

    System.out.println(" a- b is " + (a-b)); 
    System.out.println(" c- d is " + (c-d)); 
    System.out.println("a1-b1 is " + (a1-b1)); 
    System.out.println("c1-d1 is " + (c1-d1)); 

}

Output:

 
a- b is 1.0 
c- d is 1.0 
a1-b1 is 0.9 
c1-d1 is 0.8999999999999999 

Tại sao c1-d1 không bằng 0.9?

Tôi cũng đã thử các giá trị khác nhau nhưng một thời gian nó trả lại kết quả mong đợi và một thời gian không.

+4

Tương tự như http://stackoverflow.com/questions/322749/retain-precision-with-doubles-in-java – tjg184

+1

này có thể giúp http: //epramono.blogspot .com/2005/01/double-vs-bigdecimal.html – kosa

+0

Xem thêm http://mindprod.com/jgloss/floatingpoint.html –

Trả lời

3

Trong khi bạn có thể đã nghe nói về lỗi làm tròn, bạn có thể tự hỏi tại sao bạn có lỗi làm tròn ở đây.

float a1 = 1.4f; 
float b1 = 0.5f; 
double c1 = 1.4; 
double d1 = 0.5; 

System.out.println(new BigDecimal(a1) + " - " + new BigDecimal(b1) + " is " + 
     new BigDecimal(a1).subtract(new BigDecimal(b1)) + " or as a float is " + (a1 - b1)); 
System.out.println(new BigDecimal(c1) + " - " + new BigDecimal(d1) + " is " + 
     new BigDecimal(c1).subtract(new BigDecimal(d1)) + " or as a double is " + (c1 - d1)); 

in

1.39999997615814208984375 - 0.5 is 0.89999997615814208984375 or as a float is 0.9 
1.399999999999999911182158029987476766109466552734375 - 0.5 is 
    0.899999999999999911182158029987476766109466552734375 
    or as a double is 0.8999999999999999 

Như bạn có thể thấy, không float cũng không double thể đại diện cho các giá trị chính xác, và khi float hay double được in, một số làm tròn xảy ra để ẩn này từ bạn. Trong trường hợp này của float, làm tròn đến 7 chữ số thập phân cho số bạn mong đợi. Trong trường hợp gấp đôi có 16 chữ số chính xác, lỗi làm tròn hiển thị.

Là @Eric Postpischil, lưu ý xem hoạt động float hoặc double có lỗi làm tròn phụ thuộc hoàn toàn vào các giá trị được sử dụng hay không. Trong tình huống này, đó là phao có vẻ chính xác hơn, thậm chí thông qua giá trị được biểu diễn là từ 0,9 so với giá trị kép.

Tóm lại: nếu bạn định sử dụng float hoặc double, bạn nên sử dụng chiến lược làm tròn hợp lý. Nếu bạn không thể làm điều này, hãy sử dụng BigDecimal.

System.out.printf("a1 - b1 is %.2f%n", (a1 - b1)); 
System.out.printf("c1 - d1 is %.2f%n", (c1 - d1)); 

in

a1 - b1 is 0.90 
c1 - d1 is 0.90 

Khi bạn in một float hay double, nó giả định rằng giá trị thập phân ngắn gần nhất là một trong những bạn thực sự muốn. tức là trong vòng 0,5 ulp.

Ví dụ:

double d = 1.4 - 0.5; 
float f = d; 
System.out.println("d = " + d + " f = " + f); 

in

d = 0.8999999999999999 f = 0.9 
+2

Câu “Trong trường hợp gấp đôi có 16 chữ số chính xác, lỗi làm tròn có thể nhìn thấy” gợi ý rằng giá trị kép được hiển thị với nhiều chữ số hơn vì gấp đôi có độ chính xác cao hơn. Đó không phải là tình huống. Đối với mỗi loại, float và double, cho dù kết quả trừ đi .5 cũng là giá trị có thể biểu diễn gần nhất với kết quả chính xác (khi các số nguyên ban đầu được sử dụng) có hiệu quả xảy ra. Nó là một chức năng của những gì bit xảy ra để làm theo vị trí nơi làm tròn xảy ra, không phải là một chức năng của bao nhiêu bit được sử dụng. Ví dụ: thử “1.2f - .5f” và “1.2 - .5”. –

+0

@EricPostpischil Cảm ơn bạn. Như nó đã được viết, nó không rõ ràng những gì tôi có ý nghĩa. –

1

Tài liệu Java cho println đề cập (thông qua một số liên kết) đến tài liệu cho toString. Số documentation for toString cho biết số chữ số được in cho float hoặc double là số cần thiết để phân biệt duy nhất giá trị từ các giá trị có thể biểu diễn liền kề trong cùng một loại.

Khi “1.4f” được chuyển thành phao, kết quả là 1.39999997615814208984375 (trong dấu phẩy động thập lục phân, 0x1.666666p + 0). Khi .5 bị trừ, kết quả là 0.89999997615814208984375 (0x1.ccccccp-1). Khi nó xảy ra, phao này cũng là phao gần nhất với .9. Vì vậy, khi nó được in, “.9” được in.

Khi “1,4” được chuyển đổi thành gấp đôi, kết quả là 1,399999999999999911182158029987476766109466552734375 (0x1.6666666666666p + 0). Khi .5 bị trừ, kết quả là 0.899999999999999911182158029987476766109466552734375 (0x1.cccccccccccccp-1). Đây là không phải là gấp đôi gần nhất là .9, dưới dạng 0.90000000000000002220446049250313080847263336181640625 (0x1.ccccccccccccdp-1) gần hơn. Do đó, khi nó được in, đặc điểm kỹ thuật Java yêu cầu giá trị được in mịn hơn, phân biệt nó với .9. Kết quả, “0.8999999999999999”, thể hiện chính xác giá trị thực tế.

Các vấn đề liên quan