Nhân đôi so với BigDecimal?


Câu trả lời:


446

A BigDecimallà một cách chính xác để đại diện cho số. A Doublecó độ chính xác nhất định. Làm việc với gấp đôi độ lớn khác nhau (nói d1=1000.0d2=0.001) có thể dẫn đến việc 0.001bị bỏ rơi hoàn toàn khi tổng kết vì sự khác biệt về cường độ là rất lớn. Với BigDecimalđiều này sẽ không xảy ra.

Nhược điểm của BigDecimalnó là chậm hơn và khó hơn một chút để lập trình các thuật toán theo cách đó (do + - */không bị quá tải).

Nếu bạn đang giao dịch với tiền, hoặc chính xác là phải, sử dụng BigDecimal. Nếu không Doublescó xu hướng là đủ tốt.

Tôi khuyên bạn nên đọc javadoc của BigDecimalkhi họ làm giải thích mọi thứ tốt hơn tôi làm ở đây :)


Đúng, tôi đang tính giá cổ phiếu nên tôi tin BigDecimal là hữu ích trong trường hợp này.
Trương Hà

5
@Truong Ha: Khi làm việc với giá bạn muốn sử dụng BigDecimal. Và nếu bạn lưu trữ chúng trong cơ sở dữ liệu, bạn muốn một cái gì đó tương tự.
extraneon

98
Nói rằng "BigDecimal là một cách chính xác để biểu diễn các con số" là sai lệch. 1/3 và 1/7 không thể được thể hiện chính xác trong hệ thống số 10 cơ sở (BigDecimal) hoặc trong hệ thống số 2 cơ sở (float hoặc double). 1/3 có thể được thể hiện chính xác trong cơ sở 3, cơ sở 6, cơ sở 9, cơ sở 12, v.v. và 1/7 có thể được thể hiện chính xác trong cơ sở 7, cơ sở 14, cơ sở 21, v.v. Ưu điểm của BigDecimal là độ chính xác tùy ý và con người đã quen với các lỗi làm tròn mà bạn nhận được ở cơ sở 10.
procrastinate_later

3
Điểm hay về việc nó chậm hơn, giúp tôi hiểu lý do tại sao mã cân bằng tải Ribbon của Netflix xử lý gấp đôi, và sau đó có các dòng như thế này:if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
michaelok

@extraneon Tôi nghĩ bạn muốn nói "nếu độ chính xác là bắt buộc, hãy sử dụng BigDecimal", Double sẽ có nhiều "độ chính xác" hơn (nhiều chữ số hơn).
jspinella

164

Tiếng Anh của tôi không tốt nên tôi chỉ viết một ví dụ đơn giản ở đây.

    double a = 0.02;
    double b = 0.03;
    double c = b - a;
    System.out.println(c);

    BigDecimal _a = new BigDecimal("0.02");
    BigDecimal _b = new BigDecimal("0.03");
    BigDecimal _c = _b.subtract(_a);
    System.out.println(_c);

Đầu ra chương trình:

0.009999999999999998
0.01

Ai đó vẫn muốn sử dụng gấp đôi? ;)


11
@eldjon Điều đó không đúng, Hãy xem ví dụ này: BigDecimal hai = new BigDecimal ("2"); BigDecimal tám = BigDecimal mới ("8"); System.out.println (hai.divide (tám)); Điều này in ra 0,25.
Ludvig W

4
đôi forevr: D
vach

Tuy nhiên, nếu bạn sử dụng float thay vì bạn có cùng độ chính xác so với BigDecimal trong trường hợp đó nhưng hiệu suất tốt hơn
EliuX

3
@EliuX Float có thể hoạt động với 0,03-0,02, nhưng các giá trị khác vẫn không chính xác: System.out.println(0.003f - 0.002f);BigDecimal chính xác:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Martin

50

Có hai sự khác biệt chính từ đôi:

  • Độ chính xác tùy ý, tương tự như BigInteger, chúng có thể chứa số lượng chính xác và kích thước tùy ý
  • Cơ sở 10 thay vì Cơ sở 2, BigDecimal là tỷ lệ n * 10 ^ trong đó n là số nguyên có chữ ký lớn tùy ý và tỷ lệ có thể được coi là số chữ số để di chuyển dấu thập phân sang trái hoặc phải

Lý do bạn nên sử dụng BigDecimal để tính toán tiền tệ không phải vì nó có thể đại diện cho bất kỳ số nào, mà nó có thể đại diện cho tất cả các số có thể được biểu thị trong khái niệm thập phân và bao gồm hầu như tất cả các số trong thế giới tiền tệ (bạn không bao giờ chuyển 1/3 $ để một người nào đó).


2
Câu trả lời này thực sự giải thích sự khác biệt và lý do sử dụng BigDecimal hơn gấp đôi. Hiệu suất quan tâm là thứ yếu.
Vortex

Điều này không đúng 100%. Bạn đã viết rằng BigDecimal là "n * 10 ^ scale". Java chỉ làm điều đó cho các số âm. Vì vậy, chính xác sẽ là: "unscaledValue × 10 ^ -scale". Đối với các số dương, BigDecimal bao gồm "giá trị không phân chia số nguyên chính xác tùy ý và thang số nguyên 32 bit" trong khi thang đo là số chữ số ở bên phải dấu thập phân.
bàn tay của NOD

25

Nếu bạn viết ra một giá trị 1 / 7phân số như giá trị thập phân bạn nhận được

1/7 = 0.142857142857142857142857142857142857142857...

với một chuỗi vô tận 142857. Vì bạn chỉ có thể viết một số chữ số hữu hạn, chắc chắn bạn sẽ đưa ra lỗi làm tròn (hoặc cắt bớt).

Các số như 1/10hoặc được 1/100biểu thị dưới dạng số nhị phân có một phần phân số cũng có số chữ số vô hạn sau dấu thập phân:

1/10 = binary 0.0001100110011001100110011001100110...

Doubles lưu trữ các giá trị dưới dạng nhị phân và do đó có thể giới thiệu một lỗi chỉ bằng cách chuyển đổi một số thập phân thành số nhị phân, thậm chí không thực hiện bất kỳ số học nào.

BigDecimalMặt khác, số thập phân (như ), lưu trữ từng chữ số thập phân. Điều này có nghĩa là loại thập phân không chính xác hơn loại dấu phẩy nhị phân hoặc loại điểm cố định theo nghĩa chung (nghĩa là nó không thể lưu trữ 1/7mà không mất độ chính xác), nhưng chính xác hơn đối với các số có số chữ số thập phân hữu hạn như thường là trường hợp để tính tiền.

Java BigDecimalcó lợi thế bổ sung là nó có thể có số chữ số tùy ý (nhưng hữu hạn) ở cả hai phía của dấu thập phân, chỉ giới hạn bởi bộ nhớ khả dụng.


7

BigDecimal là thư viện số chính xác tùy ý của Oracle. BigDecimal là một phần của ngôn ngữ Java và rất hữu ích cho nhiều ứng dụng khác nhau, từ tài chính đến khoa học (đó là loại sáng).

Không có gì sai khi sử dụng gấp đôi cho các tính toán nhất định. Tuy nhiên, giả sử bạn muốn tính toán Math.Pi * Math.Pi / 6, nghĩa là giá trị của Hàm Riemann Zeta cho một đối số thực của hai (một dự án tôi hiện đang làm). Phân chia dấu phẩy động cho bạn một vấn đề đau đớn về lỗi làm tròn.

BigDecimal, mặt khác, bao gồm nhiều tùy chọn để tính toán biểu thức cho độ chính xác tùy ý. Các phương thức thêm, nhân và chia như được mô tả trong tài liệu Oracle bên dưới "thay thế" của +, * và / trong Thế giới Java BigDecimal:

http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html

Phương thức so sánh đặc biệt hữu ích trong khi và cho các vòng lặp.

Tuy nhiên, hãy cẩn thận khi sử dụng các hàm tạo cho BigDecimal. Hàm tạo chuỗi rất hữu ích trong nhiều trường hợp. Ví dụ, mã

BigDecimal onethird = new BigDecimal ("0.33333333333");

sử dụng biểu diễn chuỗi bằng 1/3 để biểu thị số lặp lại vô hạn đó đến một mức độ chính xác được chỉ định. Lỗi làm tròn rất có thể ở đâu đó sâu bên trong JVM đến nỗi các lỗi làm tròn sẽ không làm phiền hầu hết các tính toán thực tế của bạn. Tuy nhiên, từ kinh nghiệm cá nhân, tôi đã thấy vòng quanh leo lên. Phương thức setScale rất quan trọng trong các vấn đề này, như có thể thấy từ tài liệu của Oracle.


BigDecimal là một phần của thư viện số chính xác tùy ý của Java . "Trong nhà" là khá vô nghĩa trong bối cảnh này, đặc biệt là khi nó được viết bởi IBM.
Hầu tước Lorne

@EJP: Tôi đã xem xét lớp BigDecimal và được biết rằng chỉ một phần trong số đó được viết bởi IBM. Nhận xét bản quyền dưới đây: /* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
realPK

7

Nếu bạn đang xử lý tính toán, có những luật về cách bạn nên tính toán và độ chính xác bạn nên sử dụng. Nếu bạn thất bại rằng bạn sẽ làm điều gì đó bất hợp pháp. Lý do thực sự duy nhất là biểu diễn bit của các trường hợp thập phân không chính xác. Như Basil chỉ đơn giản là, một ví dụ là lời giải thích tốt nhất. Chỉ để bổ sung cho ví dụ của anh ấy, đây là những gì xảy ra:

static void theDoubleProblem1() {
    double d1 = 0.3;
    double d2 = 0.2;
    System.out.println("Double:\t 0,3 - 0,2 = " + (d1 - d2));

    float f1 = 0.3f;
    float f2 = 0.2f;
    System.out.println("Float:\t 0,3 - 0,2 = " + (f1 - f2));

    BigDecimal bd1 = new BigDecimal("0.3");
    BigDecimal bd2 = new BigDecimal("0.2");
    System.out.println("BigDec:\t 0,3 - 0,2 = " + (bd1.subtract(bd2)));
}

Đầu ra:

Double:  0,3 - 0,2 = 0.09999999999999998
Float:   0,3 - 0,2 = 0.10000001
BigDec:  0,3 - 0,2 = 0.1

Ngoài ra, chúng tôi có:

static void theDoubleProblem2() {
    double d1 = 10;
    double d2 = 3;
    System.out.println("Double:\t 10 / 3 = " + (d1 / d2));

    float f1 = 10f;
    float f2 = 3f;
    System.out.println("Float:\t 10 / 3 = " + (f1 / f2));

    // Exception! 
    BigDecimal bd3 = new BigDecimal("10");
    BigDecimal bd4 = new BigDecimal("3");
    System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4)));
}

Cung cấp cho chúng tôi đầu ra:

Double:  10 / 3 = 3.3333333333333335
Float:   10 / 3 = 3.3333333
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion

Nhưng:

static void theDoubleProblem2() {
    BigDecimal bd3 = new BigDecimal("10");
    BigDecimal bd4 = new BigDecimal("3");
    System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4, 4, BigDecimal.ROUND_HALF_UP)));
}

Có đầu ra:

BigDec:  10 / 3 = 3.3333 
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.