Helpex - Trao đổi & giúp đỡ Đăng nhập
828

Tôi luôn được dặn là không bao giờ đại diện cho tiền doublehoặc floatcác loại, và lần này tôi đặt ra câu hỏi cho bạn: tại sao?

Tôi chắc chắn có một lý do rất tốt, tôi chỉ đơn giản là không biết nó là gì.

828 hữu ích 4 bình luận 249k xem chia sẻ
13 trả lời 13
871

Bởi vì số float và double không thể đại diện chính xác cho 10 bội số cơ bản mà chúng ta sử dụng để kiếm tiền. Vấn đề này không chỉ dành cho Java, nó dành cho bất kỳ ngôn ngữ lập trình nào sử dụng các kiểu dấu phẩy động cơ sở 2.

Trong cơ sở 10, bạn có thể viết 10,25 là 1025 * 10 -2 (số nguyên nhân với lũy thừa 10). Các số dấu phẩy động của IEEE-754 là khác nhau, nhưng một cách rất đơn giản để suy nghĩ về chúng là nhân với một lũy thừa hai thay vào đó. Chẳng hạn, bạn có thể nhìn vào 164 * 2 -4 (số nguyên nhân với lũy thừa bằng hai), cũng bằng 10,25. Đó không phải là cách các con số được biểu diễn trong bộ nhớ, nhưng ý nghĩa toán học là như nhau.

Ngay cả trong cơ sở 10, ký hiệu này không thể biểu thị chính xác hầu hết các phân số đơn giản. Chẳng hạn, bạn không thể biểu thị 1/3: biểu diễn thập phân đang lặp lại (0,3333 ...), do đó không có số nguyên hữu hạn mà bạn có thể nhân với lũy thừa 10 để có được 1/3. Bạn có thể giải quyết một chuỗi dài 3 và một số mũ nhỏ, như 333333333 * 10 -10 , nhưng điều đó không chính xác: nếu bạn nhân số đó với 3, bạn sẽ không nhận được 1.

Tuy nhiên, với mục đích đếm tiền, ít nhất là đối với các quốc gia có tiền được định giá theo mức độ lớn của đồng đô la Mỹ, thông thường tất cả những gì bạn cần là có thể lưu trữ bội số 10 -2 , vì vậy điều đó không thực sự quan trọng 1/3 không thể được đại diện.

Vấn đề với số float và double là phần lớn các số giống như tiền không có đại diện chính xác là số nguyên nhân với lũy thừa bằng 2. Trên thực tế, bội số duy nhất của 0,01 từ 0 đến 1 (rất có ý nghĩa khi giao dịch bằng tiền vì chúng là số nguyên xu) có thể được biểu diễn chính xác dưới dạng số dấu phẩy động nhị phân IEEE-754 là 0, 0,25, 0,5, 0,75 và 1. Tất cả các số khác đều bị giảm một lượng nhỏ. Tương tự như ví dụ 0.333333, nếu bạn lấy giá trị dấu phẩy động cho 0,1 và bạn nhân nó với 10, bạn sẽ không nhận được 1.

Đại diện cho tiền như là một doublehoặc floatcó thể sẽ nhìn tốt lúc đầu như các phần mềm đạn ra khỏi sai sót nhỏ, nhưng khi bạn thực hiện bổ sung hơn về cộng, trừ, nhân, chia trên số không chính xác, sai sót sẽ cộng gộp lại và bạn sẽ kết thúc với giá trị mà là rõ ràng không chính xác. Điều này làm cho phao và nhân đôi không đủ để giao dịch với tiền, trong đó cần có độ chính xác hoàn hảo cho bội số của sức mạnh cơ sở 10.

Một giải pháp hoạt động với bất kỳ ngôn ngữ nào là sử dụng số nguyên thay thế và đếm xu. Chẳng hạn, 1025 sẽ là 10,25 đô la. Một số ngôn ngữ cũng có các loại tích hợp để giao dịch với tiền. Trong số những người khác, Java có BigDecimallớp và C # có decimalloại.

871 hữu ích 5 bình luận chia sẻ
284

Từ Bloch, J., Java hiệu quả, tái bản lần 2, Mục 48:

Các loại floatdoubleđặc biệt không phù hợp để tính toán tiền tệ vì không thể biểu thị 0,1 (hoặc bất kỳ sức mạnh tiêu cực nào khác của mười) là một floathoặc doublechính xác.

Ví dụ: giả sử bạn có $ 1,03 và bạn chi 42c. Bạn còn lại bao nhiêu tiền

System.out.println(1.03 - .42);

in ra 0.6100000000000001.

Cách đúng đắn để giải quyết vấn đề này là sử dụng BigDecimal, inthoặc long để tính toán tiền tệ.

284 hữu ích 5 bình luận chia sẻ
67

Đây không phải là vấn đề chính xác, cũng không phải là vấn đề chính xác. Đó là vấn đề đáp ứng sự mong đợi của những người sử dụng cơ sở 10 để tính toán thay vì cơ sở 2. Ví dụ: sử dụng gấp đôi để tính toán tài chính không tạo ra câu trả lời "sai" theo nghĩa toán học, nhưng nó có thể tạo ra câu trả lời không phải là những gì được mong đợi trong một ý nghĩa tài chính.

Ngay cả khi bạn làm tròn kết quả của mình vào phút cuối trước khi xuất, đôi khi bạn vẫn có thể nhận được kết quả bằng cách sử dụng gấp đôi không phù hợp với mong đợi.

Sử dụng máy tính hoặc tính toán kết quả bằng tay, chính xác là 1,40 * 165 = 231. Tuy nhiên, bên trong sử dụng nhân đôi, trên môi trường trình biên dịch / hệ điều hành của tôi, nó được lưu trữ dưới dạng số nhị phân gần 230.99999 ... vì vậy, nếu bạn cắt bớt số, bạn nhận được 230 thay vì 231. Bạn có thể lập luận rằng làm tròn thay vì cắt ngắn sẽ đã đưa ra kết quả mong muốn của 231. Điều đó là đúng, nhưng làm tròn luôn liên quan đến việc cắt ngắn. Dù bạn sử dụng kỹ thuật làm tròn nào, vẫn có những điều kiện biên như thế này sẽ làm tròn xuống khi bạn mong đợi nó sẽ làm tròn. Chúng hiếm đến mức chúng thường không được tìm thấy thông qua thử nghiệm hoặc quan sát thông thường. Bạn có thể phải viết một số mã để tìm kiếm các ví dụ minh họa các kết quả không hoạt động như mong đợi.

Giả sử bạn muốn làm tròn một cái gì đó đến đồng xu gần nhất. Vì vậy, bạn lấy kết quả cuối cùng của mình, nhân với 100, thêm 0,5, cắt ngắn, sau đó chia kết quả cho 100 để lấy lại đồng xu. Nếu số nội bộ bạn đã lưu trữ là 3.46499999 .... thay vì 3.465, bạn sẽ nhận được 3,46 thay vì 3,47 khi bạn làm tròn số đến đồng xu gần nhất. Nhưng các tính toán cơ sở 10 của bạn có thể đã chỉ ra rằng câu trả lời phải chính xác là 3.465, trong đó rõ ràng nên làm tròn đến 3,47, không giảm xuống 3,46. Những điều này đôi khi xảy ra trong cuộc sống thực khi bạn sử dụng gấp đôi để tính toán tài chính. Nó là hiếm, vì vậy nó thường không được chú ý là một vấn đề, nhưng nó xảy ra.

Nếu bạn sử dụng cơ sở 10 cho các tính toán nội bộ của mình thay vì nhân đôi, các câu trả lời luôn chính xác là những gì con người mong đợi, giả sử không có lỗi nào khác trong mã của bạn.

67 hữu ích 5 bình luận chia sẻ
46

Tôi đang gặp rắc rối bởi một số trong những phản ứng này. Tôi nghĩ rằng đôi và phao có một vị trí trong tính toán tài chính. Chắc chắn, khi cộng và trừ các khoản tiền không phân đoạn sẽ không mất độ chính xác khi sử dụng các lớp số nguyên hoặc các lớp BigDecimal. Nhưng khi thực hiện các thao tác phức tạp hơn, bạn thường kết thúc với kết quả đi ra một vài hoặc nhiều vị trí thập phân, bất kể bạn lưu trữ các số như thế nào. Vấn đề là làm thế nào bạn trình bày kết quả.

Nếu kết quả của bạn nằm ở đường biên giữa được làm tròn lên và làm tròn xuống, và đồng xu cuối cùng đó thực sự có vấn đề, có lẽ bạn nên nói với người xem rằng câu trả lời gần ở giữa - bằng cách hiển thị nhiều chữ số thập phân hơn.

Vấn đề với nhân đôi, và nhiều hơn với phao, là khi chúng được sử dụng để kết hợp số lượng lớn và số lượng nhỏ. Trong java,

System.out.println(1000000.0f + 1.2f - 1000000.0f);

kết quả trong

1.1875
46 hữu ích 3 bình luận chia sẻ
36

Phao và đôi là gần đúng. Nếu bạn tạo một BigDecimal và chuyển một float vào hàm tạo, bạn sẽ thấy những gì float thực sự bằng:

groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375

đây có lẽ không phải là cách bạn muốn đại diện cho $ 1,01.

Vấn đề là thông số kỹ thuật của IEEE không có cách biểu diễn chính xác tất cả các phân số, một số trong số chúng kết thúc dưới dạng phân số lặp lại để bạn kết thúc với các lỗi gần đúng. Vì kế toán thích những thứ được đưa ra chính xác với đồng xu và khách hàng sẽ khó chịu nếu họ thanh toán hóa đơn và sau khi thanh toán được xử lý, họ nợ 0,01 và họ bị tính phí hoặc không thể đóng tài khoản của họ, tốt hơn nên sử dụng các loại chính xác như thập phân (bằng C #) hoặc java.math.BigDecimal trong Java.

Không phải là lỗi không thể kiểm soát được nếu bạn làm tròn: xem bài viết này của Peter Lawrey . Nó chỉ dễ dàng hơn để không phải làm tròn ở nơi đầu tiên. Hầu hết các ứng dụng xử lý tiền không đòi hỏi nhiều toán học, các hoạt động bao gồm thêm các thứ hoặc phân bổ số tiền vào các nhóm khác nhau. Giới thiệu điểm nổi và làm tròn chỉ làm phức tạp mọi thứ.

36 hữu ích 2 bình luận chia sẻ
17

Tôi sẽ có nguy cơ bị hạ thấp, nhưng tôi nghĩ rằng sự không phù hợp của số dấu phẩy động để tính toán tiền tệ được đánh giá quá cao. Miễn là bạn chắc chắn rằng bạn thực hiện đúng cách làm tròn và có đủ các chữ số có nghĩa để làm việc để chống lại sự không phù hợp của biểu diễn thập phân nhị phân được giải thích bởi zneak, sẽ không có vấn đề gì.

Những người tính toán bằng tiền tệ trong Excel luôn sử dụng số float chính xác gấp đôi (không có loại tiền nào trong Excel) và tôi vẫn chưa thấy ai phàn nàn về lỗi làm tròn.

Tất nhiên, bạn phải ở trong lý trí; vd cây sào.

17 hữu ích 2 bình luận chia sẻ
17

Mặc dù đúng là loại dấu phẩy động chỉ có thể biểu thị dữ liệu thập phân gần đúng, nhưng cũng đúng là nếu một số làm tròn số với độ chính xác cần thiết trước khi trình bày chúng, người ta sẽ có được kết quả chính xác. Thông thường.

Thông thường vì loại kép có độ chính xác dưới 16 hình. Nếu bạn yêu cầu độ chính xác tốt hơn thì đó không phải là một loại phù hợp. Cũng gần đúng có thể tích lũy.

Phải nói rằng ngay cả khi bạn sử dụng số học điểm cố định, bạn vẫn phải làm tròn số, không phải vì BigInteger và BigDecimal có lỗi nếu bạn có được số thập phân định kỳ. Vì vậy, có một xấp xỉ cũng ở đây.

Ví dụ, COBOL, trong lịch sử được sử dụng để tính toán tài chính, có độ chính xác tối đa là 18 con số. Vì vậy, thường có một làm tròn ngầm.

Kết luận, theo tôi, gấp đôi không phù hợp với độ chính xác 16 chữ số của nó, có thể không đủ, không phải vì nó gần đúng.

Hãy xem xét đầu ra sau đây của chương trình tiếp theo. Nó cho thấy rằng sau khi làm tròn gấp đôi cho kết quả tương tự như BigDecimal cho đến độ chính xác 16.

Precision 14
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611

Precision 15
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110

Precision 16
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101

Precision 17
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611011
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611013

Precision 18
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110111
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110125

Precision 19
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101111
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101252

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.MathContext;

public class Exercise {
    public static void main(String[] args) throws IllegalArgumentException,
            SecurityException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {
        String amount = "56789.012345";
        String quantity = "1111111111";
        int [] precisions = new int [] {14, 15, 16, 17, 18, 19};
        for (int i = 0; i < precisions.length; i++) {
            int precision = precisions[i];
            System.out.println(String.format("Precision %d", precision));
            System.out.println("------------------------------------------------------");
            execute("BigDecimalNoRound", amount, quantity, precision);
            execute("DoubleNoRound", amount, quantity, precision);
            execute("BigDecimal", amount, quantity, precision);
            execute("Double", amount, quantity, precision);
            System.out.println();
        }
    }

    private static void execute(String test, String amount, String quantity,
            int precision) throws IllegalArgumentException, SecurityException,
            IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {
        Method impl = Exercise.class.getMethod("divideUsing" + test, String.class,
                String.class, int.class);
        String price;
        try {
            price = (String) impl.invoke(null, amount, quantity, precision);
        } catch (InvocationTargetException e) {
            price = e.getTargetException().getMessage();
        }
        System.out.println(String.format("%-30s: %s / %s = %s", test, amount,
                quantity, price));
    }

    public static String divideUsingDoubleNoRound(String amount,
            String quantity, int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        String price = Double.toString(price0);
        return price;
    }

    public static String divideUsingDouble(String amount, String quantity,
            int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        MathContext precision0 = new MathContext(precision);
        String price = new BigDecimal(price0, precision0)
                .toString();
        return price;
    }

    public static String divideUsingBigDecimal(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);
        MathContext precision0 = new MathContext(precision);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0, precision0);

        // presentation
        String price = price0.toString();
        return price;
    }

    public static String divideUsingBigDecimalNoRound(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0);

        // presentation
        String price = price0.toString();
        return price;
    }
}
17 hữu ích 0 bình luận chia sẻ
16

Kết quả của số dấu phẩy động là không chính xác, điều này làm cho chúng không phù hợp với bất kỳ phép tính tài chính nào đòi hỏi kết quả chính xác và không gần đúng. float và double được thiết kế để tính toán kỹ thuật và khoa học và nhiều lần không tạo ra kết quả chính xác, kết quả của phép tính dấu phẩy động có thể thay đổi từ JVM sang JVM. Xem ví dụ dưới đây về BigDecimal và double primitive được sử dụng để biểu thị giá trị tiền, khá rõ ràng rằng phép tính dấu phẩy động có thể không chính xác và người ta nên sử dụng BigDecimal để tính toán tài chính.

    // floating point calculation
    final double amount1 = 2.0;
    final double amount2 = 1.1;
    System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2));

    // Use BigDecimal for financial calculation
    final BigDecimal amount3 = new BigDecimal("2.0");
    final BigDecimal amount4 = new BigDecimal("1.1");
    System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));

Đầu ra:

difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9
16 hữu ích 1 bình luận chia sẻ
7

Như đã nói trước đó "Đại diện cho tiền gấp đôi hoặc nổi có thể sẽ trông tốt ngay từ đầu khi phần mềm xử lý các lỗi nhỏ, nhưng khi bạn thực hiện nhiều phép cộng, phép trừ, phép nhân và phép chia trên các số không chính xác, bạn sẽ mất nhiều hơn và chính xác hơn khi các lỗi cộng lại. Điều này làm cho số tiền thả nổi và nhân đôi không đủ để xử lý tiền, trong đó cần có độ chính xác hoàn hảo cho bội số của sức mạnh cơ sở 10 ".

Cuối cùng, Java có một cách tiêu chuẩn để làm việc với Tiền tệ và Tiền!

JSR 354: API tiền và tiền tệ

JSR 354 cung cấp API để thể hiện, vận chuyển và thực hiện các tính toán toàn diện với Tiền và Tiền tệ. Bạn có thể tải nó từ liên kết này:

JSR 354: Tải xuống API tiền và tiền tệ

Các đặc điểm kỹ thuật bao gồm những điều sau đây:

  1. Một API để xử lý, ví dụ như số tiền và tiền tệ
  2. API để hỗ trợ triển khai thay thế cho nhau
  3. Các nhà máy để tạo các thể hiện của các lớp thực hiện
  4. Chức năng tính toán, chuyển đổi và định dạng số tiền
  5. API Java để làm việc với Tiền và Tiền tệ, được lên kế hoạch đưa vào Java 9.
  6. Tất cả các lớp và giao diện đặc tả được đặt trong gói javax.money. *.

Ví dụ mẫu về JSR 354: API tiền và tiền tệ:

Một ví dụ về việc tạo Tiền tệ và in nó lên bàn điều khiển trông như thế này ::

MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

Khi sử dụng API triển khai tham chiếu, mã cần thiết đơn giản hơn nhiều:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

API cũng hỗ trợ tính toán với Mon MoneyAmounts:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));

Đơn vị tiền tệ và tiền tệ

// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);

Mon MoneyAmount có nhiều phương thức khác nhau cho phép truy cập vào loại tiền được chỉ định, số lượng, độ chính xác của nó và hơn thế nữa:

MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();

int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5

// NumberValue extends java.lang.Number. 
// So we assign numberValue to a variable of type Number
Number number = numberValue;

Tiền tệ có thể được làm tròn bằng cách sử dụng toán tử làm tròn:

CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
MonetaryAmount dollars = Money.of(12.34567, usd);
MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35

Khi làm việc với các bộ sưu tập của Mon MoneyAmounts, một số phương pháp tiện ích tuyệt vời để lọc, sắp xếp và nhóm có sẵn.

List<MonetaryAmount> amounts = new ArrayList<>();
amounts.add(Money.of(2, "EUR"));
amounts.add(Money.of(42, "USD"));
amounts.add(Money.of(7, "USD"));
amounts.add(Money.of(13.37, "JPY"));
amounts.add(Money.of(18, "USD"));

Các hoạt động kiếm tiền tùy chỉnh

// A monetary operator that returns 10% of the input MonetaryAmount
// Implemented using Java 8 Lambdas
MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {
  BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);
  BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));
  return Money.of(tenPercent, amount.getCurrency());
};

MonetaryAmount dollars = Money.of(12.34567, "USD");

// apply tenPercentOperator to MonetaryAmount
MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567

Tài nguyên:

Xử lý tiền và tiền tệ trong Java với JSR 354

Nhìn vào API tiền và tiền tệ Java 9 (JSR 354)

Xem thêm: JSR 354 - Tiền tệ và tiền

7 hữu ích 0 bình luận chia sẻ
4

Nếu tính toán của bạn bao gồm các bước khác nhau, số học chính xác tùy ý sẽ không bao gồm bạn 100%.

Cách đáng tin cậy duy nhất để sử dụng biểu diễn kết quả hoàn hảo (Sử dụng loại dữ liệu Phân số tùy chỉnh sẽ phân chia các hoạt động theo bước cho bước cuối cùng) và chỉ chuyển đổi thành ký hiệu thập phân ở bước cuối cùng.

Độ chính xác tùy ý sẽ không giúp ích vì luôn có thể có các số có số thập phân rất nhiều hoặc một số kết quả như 0,6666666 ... Không có biểu diễn tùy ý sẽ bao gồm ví dụ cuối cùng. Vì vậy, bạn sẽ có lỗi nhỏ trong từng bước.

Lỗi này sẽ bổ sung, cuối cùng có thể trở nên không dễ dàng bỏ qua nữa. Điều này được gọi là Tuyên truyền lỗi .

4 hữu ích 0 bình luận chia sẻ
2

Hầu hết các câu trả lời đã nhấn mạnh lý do tại sao người ta không nên sử dụng gấp đôi để tính toán tiền và tiền tệ. Và tôi hoàn toàn đồng ý với họ.

Điều đó không có nghĩa là mặc dù đôi đó không bao giờ có thể được sử dụng cho mục đích đó.

Tôi đã làm việc trên một số dự án với yêu cầu gc rất thấp và việc có các đối tượng BigDecimal là một đóng góp lớn cho chi phí đó.

Đó là sự thiếu hiểu biết về đại diện kép và thiếu kinh nghiệm trong việc xử lý tính chính xác và chính xác mang lại gợi ý khôn ngoan này.

Bạn có thể làm cho nó hoạt động nếu bạn có thể xử lý các yêu cầu chính xác và chính xác của dự án của bạn, điều này phải được thực hiện dựa trên phạm vi giá trị kép nào được xử lý.

Bạn có thể tham khảo phương pháp FuzzyCompare của ổi để có thêm ý tưởng. Dung sai tham số là chìa khóa. Chúng tôi đã giải quyết vấn đề này cho một ứng dụng giao dịch chứng khoán và chúng tôi đã thực hiện một nghiên cứu toàn diện về việc dung sai sử dụng cho các giá trị số khác nhau trong các phạm vi khác nhau.

Ngoài ra, có thể có những tình huống khi bạn muốn sử dụng Trình bao bọc kép làm khóa bản đồ với bản đồ băm là việc triển khai. Rất rủi ro vì Double.equals và mã băm cho các giá trị ví dụ "0,5" & "0,6 - 0,1" sẽ gây ra một mớ hỗn độn lớn.

2 hữu ích 0 bình luận chia sẻ
1

Tôi thích sử dụng Integer hoặc Long để đại diện cho tiền tệ. BigDecimal tăng mã nguồn quá nhiều.

Bạn chỉ cần biết rằng tất cả các giá trị của bạn được tính bằng xu. Hoặc giá trị thấp nhất của bất kỳ loại tiền tệ nào bạn đang sử dụng.

1 hữu ích 5 bình luận chia sẻ
1

Nhiều câu trả lời được đăng cho câu hỏi này thảo luận về IEEE và các tiêu chuẩn xung quanh số học dấu phẩy động.

Xuất thân từ một nền tảng khoa học phi máy tính (vật lý và kỹ thuật), tôi có xu hướng nhìn vấn đề từ một khía cạnh khác. Đối với tôi, lý do tại sao tôi sẽ không sử dụng số kép hoặc số float trong phép tính toán là vì tôi sẽ mất quá nhiều thông tin.

Các lựa chọn thay thế là gì? Có rất nhiều (và nhiều trong số đó tôi không biết!).

BigDecimal trong Java có nguồn gốc từ ngôn ngữ Java. Apfloat là một thư viện chính xác tùy ý khác cho Java.

Kiểu dữ liệu thập phân trong C # là .NET thay thế cho 28 số liệu quan trọng.

SciPy (Python khoa học) có thể cũng có thể xử lý các tính toán tài chính (tôi chưa thử, nhưng tôi nghi ngờ như vậy).

Thư viện GNU Multi Precision (GMP) và Thư viện GNU MFPR là hai tài nguyên nguồn mở và miễn phí cho C và C ++.

Ngoài ra còn có các thư viện chính xác về số cho JavaScript (!) Và tôi nghĩ PHP có thể xử lý các tính toán tài chính.

Ngoài ra còn có các giải pháp độc quyền (đặc biệt, tôi nghĩ, đối với Fortran) và các giải pháp nguồn mở cũng như cho nhiều ngôn ngữ máy tính.

Tôi không phải là một nhà khoa học máy tính bằng cách đào tạo. Tuy nhiên, tôi có xu hướng nghiêng về BigDecimal trong Java hoặc thập phân trong C #. Tôi đã không thử các giải pháp khác mà tôi đã liệt kê, nhưng chúng có lẽ cũng rất tốt.

Đối với tôi, tôi thích BigDecimal vì các phương thức mà nó hỗ trợ. Số thập phân của C # rất đẹp, nhưng tôi chưa có cơ hội làm việc với nó nhiều như tôi muốn. Tôi thực hiện các tính toán khoa học về sự quan tâm của tôi trong thời gian rảnh rỗi và BigDecimal dường như hoạt động rất tốt vì tôi có thể đặt độ chính xác của các số dấu phẩy động của mình. Bất lợi cho BigDecimal? Đôi khi nó có thể bị chậm, đặc biệt nếu bạn đang sử dụng phương pháp chia.

Về tốc độ, bạn có thể xem xét các thư viện miễn phí và độc quyền trong C, C ++ và Fortran.

1 hữu ích 1 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ floating-point currency , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm

loading