Loại dữ liệu nào để sử dụng tiền trong Java? [đóng cửa]


183

Loại dữ liệu nào bạn nên sử dụng để kiếm tiền trong Java?


2
Nó phụ thuộc vào những gì bạn sẽ làm hoạt động. Vui lòng cung cấp thêm thông tin.
eversor

@eversor Bạn có thể cho tôi mô tả về loại dữ liệu nào nên được sử dụng cho các hoạt động khác nhau không?
Questborn

1
Đang thực hiện các phép tính đòi hỏi tôi phải trình bày chính xác xu.
Questborn

Bạn có thể báo trước số tiền lớn nhất mà ứng dụng của bạn sẽ cần xử lý không? Và, tính toán của bạn, chúng sẽ đơn giản (quảng cáo, v.v.) hay hoạt động tài chính phức tạp hơn?
eversor

Câu trả lời:


133

Java có Currencylớp đại diện cho mã tiền tệ ISO 4217. BigDecimallà loại tốt nhất để biểu thị các giá trị thập phân tiền tệ.

Joda Money đã cung cấp một thư viện để đại diện cho tiền.


5
Tại sao chúng ta không thể sử dụng float hoặc double thay thế?
Erran Morad

20
@Borat Sagdiyev Đây là lý do tại sao . Ngoài ra, bạn có thể tham khảo điều này .
Buhake Sindi

2
@Borat: bạn có thể nếu bạn biết những gì bạn đang làm, xem bài viết này của Peter Lawrey. nhưng có vẻ ít rắc rối khi thực hiện tất cả các thao tác làm tròn như sử dụng BigDecimals.
Nathan Hughes

35
"Nếu tôi có một xu mỗi lần tôi thấy ai đó sử dụng FLOAT để lưu trữ tiền tệ, tôi sẽ có $ 999,997634" - Bill Karwin
Collin Krawll

36

Bạn có thể sử dụng API tiền và tiền tệ (JSR 354) . Bạn có thể sử dụng API này, miễn là bạn thêm các phụ thuộc phù hợp vào dự án của mình.

Đối với Java 8, hãy thêm triển khai tham chiếu sau đây làm phụ thuộc vào pom.xml:

<dependency>
    <groupId>org.javamoney</groupId>
    <artifactId>moneta</artifactId>
    <version>1.0</version>
</dependency>

Sự phụ thuộc này sẽ liên tục thêm javax.money:money-apinhư một phụ thuộc.

Sau đó, bạn có thể sử dụng API:

package com.example.money;

import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.is;

import java.util.Locale;

import javax.money.Monetary;
import javax.money.MonetaryAmount;
import javax.money.MonetaryRounding;
import javax.money.format.MonetaryAmountFormat;
import javax.money.format.MonetaryFormats;

import org.junit.Test;

public class MoneyTest {

    @Test
    public void testMoneyApi() {
        MonetaryAmount eurAmount1 = Monetary.getDefaultAmountFactory().setNumber(1.1111).setCurrency("EUR").create();
        MonetaryAmount eurAmount2 = Monetary.getDefaultAmountFactory().setNumber(1.1141).setCurrency("EUR").create();

        MonetaryAmount eurAmount3 = eurAmount1.add(eurAmount2);
        assertThat(eurAmount3.toString(), is("EUR 2.2252"));

        MonetaryRounding defaultRounding = Monetary.getDefaultRounding();
        MonetaryAmount eurAmount4 = eurAmount3.with(defaultRounding);
        assertThat(eurAmount4.toString(), is("EUR 2.23"));

        MonetaryAmountFormat germanFormat = MonetaryFormats.getAmountFormat(Locale.GERMAN);
        assertThat(germanFormat.format(eurAmount4), is("EUR 2,23") );
    }
}

Điều gì về tuần tự hóa và lưu vào db? Định dạng nào nên được sử dụng để gửi qua dây?
Paweł Szczur

1
Tôi tin rằng Oracle đã dành những sự đồng ý bao gồm cả tiền Java trong Java 9. Thực sự là một sự xấu hổ. Nhưng câu trả lời tuyệt vời. Chúng tôi vẫn có thể sử dụng nó với Maven
borjab 7/1/2016

3
Bạn có nguồn nào để Oracle quyết định bao gồm tiền Java trong Java 9 không?
Abdull

26

Một loại tích phân đại diện cho giá trị nhỏ nhất có thể. Nói cách khác, chương trình của bạn nên nghĩ bằng xu chứ không phải bằng đô la / euro.

Điều này sẽ không ngăn bạn có gui dịch nó trở lại đô la / euro.


Hãy nhớ rằng số tiền có thể vượt quá kích thước của int
eversor

5
@eversor sẽ cần hơn 20 triệu đô la, hầu hết các ứng dụng sẽ không cần nhiều như vậy nếu chúng tồn tại lâu sẽ không đủ vì ngay cả các trang phục của chúng tôi cũng không đủ khả năng để vượt qua điều đó
ratchet freak

4
@ratchetfreak Có lẽ tốt hơn để sử dụng lâu rồi.
kẻ đánh cắp

4
Nhiều ngân hàng xử lý số tiền lớn hơn rất nhiều mà 20.000.000 đô la mỗi ngày. Điều này thậm chí không tính đến các loại tiền tệ như đồng yên với tỷ giá hối đoái lớn với đồng đô la. Các loại số nguyên có thể là tốt nhất để tránh các vấn đề làm tròn mặc dù chúng trở nên lộn xộn với các tính toán lãi suất và tỷ giá hối đoái. Tuy nhiên, tùy thuộc vào ứng dụng, bạn có thể cần loại số nguyên 64 bit.
Alchymist

Trên thực tế, các microdollar lý tưởng là nếu bạn thực hiện, ví dụ như $ 10/3 thì lỗi làm tròn (3333.3 => 3333.0) không ảnh hưởng đến giá trị cuối cùng (mặc dù trong trường hợp này nó không ảnh hưởng đến giá trị thực, mặc dù vậy nguy hiểm khi cho rằng nó sẽ không bao giờ). Điều này đặc biệt quan trọng nếu bạn thực hiện nhiều phép tính liên tiếp trước khi người dùng của bạn nhìn thấy kết quả, vì các lỗi làm tròn sẽ kết hợp.
Chris Browne


11

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


Tất cả điều này là tốt, nhưng như Federico đã đề xuất ở trên, nó có vẻ chậm hơn BigDecimal :-)) sau đó chỉ là trò đùa, nhưng tôi sẽ thử nghiệm ngay bây giờ 1 năm sau ...
kensai

6

Bạn nên sử dụng BigDecimal để thể hiện các giá trị tiền tệ. Nó cho phép bạn sử dụng nhiều chế độ làm tròn khác nhau và trong các ứng dụng tài chính, chế độ làm tròn thường là một yêu cầu khó khăn thậm chí có thể được pháp luật ủy quyền.



6

Tôi đã thực hiện một microbenchmark (JMH) để so sánh Moneta (java triển khai tiền tệ JSR 354) với BigDecimal về hiệu suất.

Đáng ngạc nhiên, hiệu suất BigDecimal dường như tốt hơn so với moneta. Tôi đã sử dụng cấu hình moneta sau:

org.javamoney.moneta.Money.defaults.precision = 19 org.javamoney.moneta.Money.defaults.roundingMode = HALF_UP

package com.despegar.bookedia.money;

import org.javamoney.moneta.FastMoney;
import org.javamoney.moneta.Money;
import org.openjdk.jmh.annotations.*;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.concurrent.TimeUnit;

@Measurement(batchSize = 5000, iterations = 10, time = 2, timeUnit =     TimeUnit.SECONDS)
@Warmup(iterations = 2)
@Threads(value = 1)
@Fork(value = 1)
@State(Scope.Benchmark)
@BenchmarkMode(Mode.Throughput)
public class BigDecimalBenchmark {

private static final Money MONEY_BASE = Money.of(1234567.3444, "EUR");
private static final Money MONEY_SUBSTRACT = Money.of(232323, "EUR");
private static final FastMoney FAST_MONEY_SUBSTRACT = FastMoney.of(232323, "EUR");
private static final FastMoney FAST_MONEY_BASE = FastMoney.of(1234567.3444, "EUR");
MathContext mc = new MathContext(10, RoundingMode.HALF_UP);

@Benchmark
public void bigdecimal_string() {
    new BigDecimal("1234567.3444").subtract(new BigDecimal("232323")).multiply(new BigDecimal("3.4"), mc).divide(new BigDecimal("5.456"), mc);
}

@Benchmark
public void bigdecimal_valueOf() {
    BigDecimal.valueOf(12345673444L, 4).subtract(BigDecimal.valueOf(232323L)).multiply(BigDecimal.valueOf(34, 1), mc).divide(BigDecimal.valueOf(5456, 3), mc);
}
@Benchmark
public void fastmoney() {
    FastMoney.of(1234567.3444, "EUR").subtract(FastMoney.of(232323, "EUR")).multiply(3.4).divide(5.456);
}

@Benchmark
public void money() {
    Money.of(1234567.3444, "EUR").subtract(Money.of(232323, "EUR")).multiply(3.4).divide(5.456);
}

@Benchmark
public void money_static(){
    MONEY_BASE.subtract(MONEY_SUBSTRACT).multiply(3.4).divide(5.456);
}

@Benchmark
public void fastmoney_static() {
    FAST_MONEY_BASE.subtract(FAST_MONEY_SUBSTRACT).multiply(3.4).divide(5.456);
    }
}

Kết quả là

Benchmark                                Mode  Cnt     Score    Error  Units
BigDecimalBenchmark.bigdecimal_string   thrpt   10   479.465 ± 26.821  ops/s
BigDecimalBenchmark.bigdecimal_valueOf  thrpt   10  1066.754 ± 40.997  ops/s
BigDecimalBenchmark.fastmoney           thrpt   10    83.917 ±  4.612  ops/s
BigDecimalBenchmark.fastmoney_static    thrpt   10   504.676 ± 21.642  ops/s
BigDecimalBenchmark.money               thrpt   10    59.897 ±  3.061  ops/s
BigDecimalBenchmark.money_static        thrpt   10   184.767 ±  7.017  ops/s

Xin vui lòng sửa tôi nếu tôi thiếu một cái gì đó


Thật thú vị, tôi sẽ chạy thử nghiệm tương tự với những thứ mới nhất trên JDK9
kensai

4

Đối với trường hợp đơn giản (một loại tiền tệ), nó là đủ Integer/ Long. Giữ tiền bằng xu (...) hoặc hàng trăm / nghìn xu (bất kỳ độ chính xác nào bạn cần với bộ chia cố định)


3

BigDecimal là loại dữ liệu tốt nhất để sử dụng cho tiền tệ.

Có rất nhiều container cho tiền tệ, nhưng tất cả chúng đều sử dụng BigDecimal làm kiểu dữ liệu cơ bản. Bạn sẽ không gặp trục trặc với BigDecimal, có thể sử dụng BigDecimal.ROUND_HALF_EVEN làm tròn.


2

Tôi thích sử dụng Tiny Type sẽ bao gồm cả hai, BigDecimal hoặc int như các câu trả lời trước đây đã đề xuất. (Tôi sẽ sử dụng gấp đôi trừ khi các vấn đề chính xác tăng lên).

Loại Tiny cung cấp cho bạn loại an toàn để bạn không nhầm lẫn một khoản tiền gấp đôi với các nhân đôi khác.


6
Mặc dù tôi cũng thích các loại nhỏ, bạn không bao giờ nên sử dụng gấp đôi để lưu trữ giá trị tiền tệ.
orien
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.