2012-10-10 15 views
10

Tôi phải lưu trữ sản phẩm của một số giá trị xác suất thực sự thấp (ví dụ: 1E-80). Sử dụng cú pháp java nguyên thủy sẽ dẫn đến số không vì dòng dưới. Tôi không muốn giá trị chuyển thành 0 vì sau này sẽ có số lớn hơn (ví dụ: 1E100) sẽ mang các giá trị trong phạm vi mà đôi có thể xử lý.Java đôi và làm việc với các giá trị thực sự nhỏ

Vì vậy, tôi đã tạo một lớp khác (MyDouble) bản thân mình hoạt động trên lưu phần cơ sở và các phần số mũ. Khi thực hiện phép tính, ví dụ phép nhân, tôi nhân các phần cơ sở và thêm số mũ.

Chương trình nhanh với kiểu kép nguyên thủy. Tuy nhiên, khi tôi sử dụng lớp của riêng tôi (MyDouble) chương trình là rất chậm. Tôi nghĩ rằng điều này là do các đối tượng mới mà tôi phải tạo ra mỗi lần để tạo ra các hoạt động đơn giản và bộ thu gom rác phải làm rất nhiều công việc khi các đối tượng không còn cần thiết nữa.

Câu hỏi của tôi là, có cách nào tốt hơn bạn nghĩ tôi có thể giải quyết vấn đề này không? Nếu không, có cách nào để tôi có thể tăng tốc chương trình với lớp của riêng mình (MyDouble) không?

[Lưu ý: tham gia các log và sau đó lấy số mũ không giải quyết vấn đề của tôi]

MyDouble lớp:

public class MyDouble { 
    public MyDouble(double base, int power){ 
    this.base = base; 
    this.power = power; 
    } 

    public static MyDouble multiply(double... values) { 
    MyDouble returnMyDouble = new MyDouble(0); 
    double prodBase = 1; 
    int prodPower = 0; 
    for(double val : values) { 
      MyDouble ad = new MyDouble(val); 
      prodBase *= ad.base; 
      prodPower += ad.power; 
     } 
     String newBaseString = "" + prodBase; 
     String[] splitted = newBaseString.split("E"); 
     double newBase = 0; int newPower = 0; 
     if(splitted.length == 2) { 
      newBase = Double.parseDouble(splitted[0]); 
      newPower = Integer.parseInt(splitted[1]); 
     } else { 
      newBase = Double.parseDouble(splitted[0]); 
      newPower = 0; 
     } 
     returnMyDouble.base = newBase; 
     returnMyDouble.power = newPower + prodPower;   
     return returnMyDouble; 
    } 
} 
+4

Tại sao bạn không sử dụng [BigDecimal] (http://docs.oracle.com/javase/1.5.0/docs/api/java/math/BigDecimal.html)? – DaoWen

+0

Hoặc 'BigInteger' –

+0

xem http://stackoverflow.com/questions/277309/java-floating-point-high-precision-library –

Trả lời

1

Tốc độ chậm có thể là do các đối tượng chuỗi trung gian được tạo ra trong các phân tách chuỗi và chuỗi.

Hãy thử điều này:

/** 
* value = base * 10^power. 
*/ 

public class MyDouble { 

    // Threshold values to determine whether given double is too small or not. 
private static final double SMALL_EPSILON = 1e-8; 
private static final double SMALL_EPSILON_MULTIPLIER = 1e8; 
private static final int SMALL_EPSILON_POWER = 8; 

private double myBase; 
private int myPower; 

public MyDouble(double base, int power){ 
    myBase = base; 
    myPower = power; 
} 

public MyDouble(double base) 
{ 
    myBase = base; 
    myPower = 0; 
    adjustPower(); 
} 

/** 
* If base value is too small, increase the base by multiplying with some number and 
* decrease the power accordingly. 
* <p> E.g 0.000 000 000 001 * 10^1 => 0.0001 * 10^8 
*/ 
private void adjustPower() 
{ 
    // Increase the base & decrease the power 
    // if given double value is less than threshold. 
    if (myBase < SMALL_EPSILON) { 
     myBase = myBase * SMALL_EPSILON_MULTIPLIER; 
     myPower -= SMALL_EPSILON_POWER; 
    } 
} 

/** 
* This method multiplies given double and updates this object. 
*/ 
public void multiply(MyDouble d) 
{ 
    myBase *= d.myBase; 
    myPower += d.myPower; 
    adjustPower(); 
} 

/** 
* This method multiplies given primitive double value with this object and update the 
* base and power. 
*/ 
public void multiply(double d) 
{ 
    multiply(new MyDouble(d)); 
} 

@Override 
public String toString() 
{ 
    return "Base:" + myBase + ", Power=" + myPower; 
} 

/** 
* This method multiplies given double values and returns MyDouble object. 
* It make sure that too small double values do not zero out the multiplication result. 
*/ 
public static MyDouble multiply(double...values) 
{ 
    MyDouble result = new MyDouble(1); 
    for (int i=0; i<values.length; i++) { 
     result.multiply(values[i]); 
    } 
    return result; 
} 

public static void main(String[] args) { 
    MyDouble r = MyDouble.multiply(1e-80, 1e100); 
    System.out.println(r); 
} 

}

Nếu đây là vẫn còn chậm cho mục đích của bạn, bạn có thể thay đổi nhân() phương pháp hoạt động trực tiếp trên nguyên thủy kép thay vì tạo một đối tượng MyDouble.

1

Tôi chắc chắn đây sẽ là một thỏa thuận tốt chậm hơn so với một đôi, nhưng có lẽ một yếu tố đóng góp lớn sẽ là thao tác String. Bạn có thể thoát khỏi điều đó và tính toán sức mạnh thông qua số học thay thế? Ngay cả số học đệ quy hoặc lặp lại có thể nhanh hơn chuyển đổi thành Chuỗi để lấy các bit của số đó.

1

Trong một ứng dụng nặng hiệu năng, bạn muốn tìm cách lưu trữ thông tin cơ bản trong nguyên thủy. Trong trường hợp này, có lẽ bạn có thể chia các byte của một biến dài hoặc khác để một phần cố định là cơ sở.

Sau đó, bạn có thể tạo các phương thức tùy chỉnh nhân dài hoặc Dài như thể chúng là gấp đôi. Bạn lấy các bit đại diện cho cơ sở và điểm kinh nghiệm, và cắt ngắn cho phù hợp.

Trong một số ý nghĩa, bạn đang tái phát minh ra bánh xe ở đây, vì bạn muốn mã byte thực hiện hiệu quả thao tác bạn đang tìm kiếm.

chỉnh sửa:

Nếu bạn muốn gắn bó với hai biến, bạn có thể sửa đổi mã của bạn chỉ đơn giản là lấy một mảng, mà sẽ nhẹ hơn nhiều so với các đối tượng. Ngoài ra, bạn cần phải loại bỏ các cuộc gọi đến bất kỳ chức năng phân tích chuỗi nào. Chúng cực kỳ chậm.

2

Bạn đang cố gắng phân tích chuỗi mỗi lần bạn thực hiện nhân. Tại sao bạn không tính toán tất cả các giá trị vào một số cấu trúc như phần thực và số mũ như bước tính toán trước và sau đó tạo các thuật toán cho phép nhân, thêm, phân chia, quyền lực và khác.

Ngoài ra, bạn có thể thêm cờ cho số lớn/nhỏ. Tôi nghĩ bạn sẽ không sử dụng cả 1e100 và 1e-100 trong một phép tính (vì vậy bạn có thể đơn giản hóa một số phép tính) và bạn có thể cải thiện thời gian tính toán cho các cặp khác nhau (lớn, lớn), (nhỏ, nhỏ), (lớn, nhỏ).

2

Bạn có thể sử dụng

BigDecimal bd = BigDecimal.ONE.scaleByPowerOfTen(-309) 
     .multiply(BigDecimal.ONE.scaleByPowerOfTen(-300)) 
     .multiply(BigDecimal.ONE.scaleByPowerOfTen(300)); 
System.out.println(bd); 

in

1E-309 

Hoặc nếu bạn sử dụng một quy mô log10

double d = -309 + -300 + 300; 
System.out.println("1E"+d); 

in

1E-309.0 
4

Cách này được giải quyết là để làm việc trong không gian log --- nó trivialises vấn đề. Khi bạn nói nó không hoạt động, bạn có thể cung cấp chi tiết cụ thể về lý do tại sao không? Khả năng tràn là một vấn đề phổ biến trong các mô hình xác suất, và tôi không nghĩ rằng tôi đã từng biết nó giải quyết bất kỳ cách nào khác.

Nhớ lại rằng nhật ký (a * b) chỉ là nhật ký (a) + nhật ký (b). Tương tự log (a/b) là log (a) - log (b). Tôi giả sử kể từ khi bạn đang làm việc với xác suất nhân của nó và phân chia đang gây ra các vấn đề dưới; nhược điểm của không gian log là bạn cần sử dụng các thủ tục đặc biệt để tính toán log (a + b), mà tôi có thể hướng dẫn bạn nếu đây là vấn đề của bạn.

Vì vậy, câu trả lời đơn giản là, làm việc trong không gian nhật ký và tái lập lại ở cuối để có được số người có thể đọc được.

+0

Tôi đã nghĩ rằng tôi không thể sử dụng nhật ký vì các điều khoản của tôi cũng liên quan đến số tiền của số mũ. Tôi đã sử dụng thủ thuật logumexp. Cuối cùng tôi đã sử dụng tất cả mọi thứ trong không gian đăng nhập vì thực hiện của riêng tôi, mặc dù đã làm việc, thực sự rất chậm. –

+0

Vâng, hầu hết mọi thứ có thể được thực hiện để làm việc trong không gian log, nhưng đối với một số hoạt động (tổng hợp là vấn đề rõ ràng) nó không tầm thường. –

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