Jakiego typu danych należy używać do pieniędzy w Javie?
Jakiego typu danych należy używać do pieniędzy w Javie?
Odpowiedzi:
Java ma Currency
klasę reprezentującą kody walut ISO 4217.
BigDecimal
jest najlepszym typem do reprezentowania wartości dziesiętnych waluty.
Joda Money zapewnił bibliotekę do reprezentowania pieniędzy.
Możesz użyć interfejsu Money and Currency API (JSR 354) . Możesz użyć tego interfejsu API pod warunkiem, że dodasz odpowiednie zależności do swojego projektu.
W przypadku języka Java 8 dodaj następującą implementację referencyjną jako zależność pom.xml
:
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>1.0</version>
</dependency>
Ta zależność zostanie tranzystorowo dodana javax.money:money-api
jako zależność.
Następnie możesz użyć interfejsu 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") );
}
}
Typ całkowy reprezentujący najmniejszą możliwą wartość. Innymi słowy, twój program powinien myśleć w centach, a nie w dolarach / euro.
Nie powinno to powstrzymywać cię przed przetłumaczeniem GUI na dolary / euro.
Można użyć BigDecimal , dobre wyjaśnienie, dlaczego nie należy używać Float lub Double można zobaczyć tutaj: Dlaczego nie użyć Double lub Float do reprezentowania waluty?
JSR 354: API pieniędzy i walut
JSR 354 zapewnia interfejs API do reprezentowania, transportu i wykonywania kompleksowych obliczeń za pomocą pieniędzy i waluty. Możesz pobrać go z tego linku:
JSR 354: Pobieranie interfejsu API pieniędzy i walut
Specyfikacja składa się z następujących rzeczy:
- Interfejs API do obsługi np. Kwot pieniężnych i walut
- Interfejsy API do obsługi wymiennych implementacji
- Fabryki do tworzenia instancji klas implementacji
- Funkcjonalność do obliczeń, konwersji i formatowania kwot pieniężnych
- Java API do pracy z pieniędzmi i walutami, które planuje się włączyć do Java 9.
- Wszystkie klasy specyfikacji i interfejsy znajdują się w pakiecie javax.money. *.
Przykładowe przykłady JSR 354: API pieniędzy i walut:
Przykład utworzenia MonetaryAmount i wydrukowania go na konsoli wygląda następująco:
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));
Podczas korzystania z referencyjnego interfejsu API implementacji niezbędny kod jest znacznie prostszy:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
Interfejs API obsługuje również obliczenia za pomocą MonetaryAmounts:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));
CurrencyUnit i MonetaryAmount
// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MonetaryAmount ma różne metody, które umożliwiają dostęp do przypisanej waluty, kwoty liczbowej, jej precyzji i nie tylko:
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;
Kwoty pieniężne można zaokrąglać za pomocą operatora zaokrąglania:
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
Podczas pracy z kolekcjami MonetaryAmounts dostępnych jest kilka przydatnych narzędzi do filtrowania, sortowania i grupowania.
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"));
Niestandardowe operacje MonetaryAmount
// 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
Zasoby:
Obsługa pieniędzy i walut w Javie z JSR 354
Patrząc na interfejs API pieniędzy i walut Java 9 (JSR 354)
Zobacz także: JSR 354 - Waluta i pieniądze
Powinieneś używać BigDecimal do reprezentowania wartości pieniężnych. Pozwala to na korzystanie z różnych trybów zaokrąglania , aw aplikacjach finansowych tryb zaokrąglania jest często trudnym wymogiem, który może być nawet wymagany przez prawo.
Chciałbym użyć Joda pieniądze
Nadal jest w wersji 0.6, ale wygląda bardzo obiecująco
Zrobiłem znak mikrodruku (JMH), aby porównać Monetę (implementację JSR 354 w walucie Java) z BigDecimal pod względem wydajności.
Zaskakujące wydaje się, że wydajność BigDecimal jest lepsza niż moneta. Użyłem następującej konfiguracji moneta:
org.javamoney.moneta.Money.defaults.precision = 19 org.javamoney.moneta.Money.defaults.roundingMode = POŁOWA
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);
}
}
Wynikające z
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
Prosimy o poprawienie mnie, jeśli czegoś brakuje
W prostym przypadku (jedna waluta) to wystarczy Integer
/ Long
. Trzymaj pieniądze w centach (...) lub setnych / tysięcznych centach (dowolna precyzja, której potrzebujesz z ustalonym dzielnikiem)
BigDecimal jest najlepszym typem danych dla waluty.
Istnieje wiele kontenerów dla waluty, ale wszystkie używają BigDecimal jako podstawowego typu danych. Nie pomylisz się z BigDecimal, prawdopodobnie używając zaokrąglania BigDecimal.ROUND_HALF_EVEN.
Lubię używać Tiny Types, które zawijałyby double, BigDecimal lub int, jak sugerowały poprzednie odpowiedzi. (Użyłbym podwójnego, chyba że pojawią się problemy z precyzją).
Mały typ zapewnia bezpieczeństwo pisania, więc nie mylisz podwójnych pieniędzy z innymi podwójnymi.