2011-08-10 27 views
5

Đối với mã sau (Java):Chuyển đổi từ đôi thành int sang Java như thế nào?

double d = (double) m/n; //m and n are integers, n>0 
int i = (int) (d * n); 

i == m 

Biểu thức cuối cùng luôn đúng? Nếu điều này không đúng, điều này luôn đúng ?:

i = (int) Math.round(d * n); 

i == m 
+1

Không phải là một câu hỏi ngớ ngẩn; câu hỏi đặt ra một số vấn đề tinh tế về số học dấu phẩy động và "khả năng phục hồi" của các số nguyên. – Nayuki

+0

rất nhiều câu hỏi nổi trên trang web này gần đây - hmm ... –

Trả lời

4

Câu hỏi thứ hai bạn đặt ra mối quan tâm về số lượng lớn ulp bằng Java.

Nếu ulp vượt quá 1/(n), thì làm tròn phép nhân sẽ không khôi phục được số nguyên gốc được chia. Thông thường, các ulps lớn hơn được kết hợp với các giá trị tăng gấp đôi lớn hơn. Một ulp kết hợp với một đôi bắt đầu vượt quá 1 vào khoảng 9E15; nếu bạn tăng gấp đôi phục hồi được xung quanh đó, sau đó bạn có thể tìm thấy vấn đề với vòng() không nhận được câu trả lời mong đợi. Vì bạn đang làm việc với các giá trị int, mặc dù, giá trị lớn nhất của tử số của bộ phận của bạn sẽ là Integer.MAX_VALUE.

Các thử nghiệm chương trình sau tất cả các giá trị nguyên dương của n để xem cái nào làm cho tiềm năng lớn nhất để làm tròn lỗi khi cố gắng khôi phục lại int chia:

public static void main(String[] args) 
    { 
    // start with large number 
    int m = Integer.MAX_VALUE; 
    double d = 0; 

    double largestError = 0; 
    int bigErrorCause = -1; 
    for (int n = 1; n < Integer.MAX_VALUE; n++) 
    { 
     d = (double) m/n; 
     double possibleError = Math.ulp(d) * n; 
     if (possibleError > largestError) 
     { 
     largestError = possibleError; 
     bigErrorCause = n; 
     } 
    } 
    System.out.println("int " + bigErrorCause + " causes at most " 
     + largestError + " error"); 
    } 

Kết quả là:

int 1073741823 gây ra tối đa 4.768371577590358E-7 lỗi

Làm tròn bằng cách sử dụng Math.round, sau đó truyền tới int sẽ khôi phục int ban đầu.

1

Về mặt toán học, điều này phải đúng. Tuy nhiên, bạn có thể sẽ nhận được các lỗi làm tròn dấu chấm động sẽ làm cho nó sai. Bạn hầu như không bao giờ nên so sánh các số chính xác của dấu phẩy động bằng cách sử dụng ==.

Bạn đang tốt hơn off so sánh chúng bằng một ngưỡng như thế này:

Math.abs(d*n - m) < 0.000001; 

Lưu ý rằng hai báo cáo nên tương đương

i = (int) (d * n); 
i = (int) Math.round(d * n); 

Tuy nhiên ví dụ, nếu d=3/2n=2, nổi lỗi điểm có thể dẫn đến i=2.999999999999 mà sau khi cắt ngắn/làm tròn là 2.

+3

Lý do của bạn về cắt ngắn là tốt. Nhưng ví dụ của bạn là xấu, bởi vì phân chia dấu phẩy động bằng 2 luôn luôn chính xác (ngoại trừ phần dưới). ;-) – Nayuki

1

Đầu tiên là d efinitely không phải lúc nào cũng đúng. Điều thứ hai tôi có thể nói có đúng, nhưng chỉ vì tôi không thể nghĩ ra một ví dụ.

Nếu n là rất lớn, nó có thể có thể là sai, tôi không chắc chắn thực sự. Tôi biết nó sẽ đúng ít nhất 99% thời gian.

5

int i = (int) (d * n); i == m;

Đây là sai cho m = 1, n = 49.

i = (int) Math.round(d * n); i == m;

trực giác của tôi nói với tôi nó phải đúng, nhưng nó có thể là khó khăn để chứng minh một cách nghiêm ngặt.

+0

Bạn có thể xác minh câu lệnh của tôi ngay cả trong JavaScript. Bạn có thể nhập mã này vào trình duyệt của mình: 'javascript: 1/49 * 49', cung cấp cho 0.9999 .... – Nayuki

+2

+1 Vì 'double' có độ chính xác 53 bit và bạn chia và nhân nó với ít hơn' 2^31' kết quả nên được tắt bởi ít hơn '1/2^21 = 2 * 1/2^22' (hệ số 2 là từ thực hiện hai thao tác). Vì vậy, làm tròn sẽ là số nguyên chính xác bởi một lề rộng. – starblue

+0

Tôi đã xóa bài đăng của mình vì tôi nghĩ rằng tôi đã sai về trường hợp thứ 2, Câu trả lời hay (tôi đã cung cấp cho bạn +1 của tôi) – MByD

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