Jakiego typu danych użyć do pieniędzy w Javie? [Zamknięte]


183

Jakiego typu danych należy używać do pieniędzy w Javie?


2
To zależy od tego, jakie operacje zamierzasz wykonać. Proszę podać więcej informacji.
— eversor

@eversor Czy możesz mi podać opis, jaki typ danych powinien być używany do różnych operacji?
— questborn

1
Robię obliczenia, które wymagają ode mnie dokładnego przedstawienia centów.
— questborn

Czy jesteś w stanie przewidzieć największą kwotę, którą Twoja aplikacja będzie musiała obsłużyć? A twoje obliczenia, czy będą one proste (reklamy itp.) Czy bardziej złożone operacje finansowe?
— eversor

Odpowiedzi:


133

Java ma Currencyklasę reprezentującą kody walut ISO 4217. BigDecimaljest najlepszym typem do reprezentowania wartości dziesiętnych waluty.

Joda Money zapewnił bibliotekę do reprezentowania pieniędzy.


5
Dlaczego nie możemy zamiast tego użyć liczby zmiennoprzecinkowej?
— Erran Morad

20
@Borat Sagdiyev To jest powód tego . Możesz również odnieść się do tego .
— Buhake Sindi

2
@ Borat: jeśli wiesz, co robisz, zapoznaj się z tym artykułem autorstwa Petera Lawreya. ale wydaje się co najmniej tak samo kłopotliwe, jak przy użyciu BigDecimals.
— Nathan Hughes

35
„Gdybym miał bilon za każdym razem, gdy widziałem, jak ktoś używa FLOAT do przechowywania waluty, miałbym 999,997634 $” - Bill Karwin
— Collin Krawll

36

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-apijako 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") );
    }
}

Co z serializacją i zapisywaniem w db? Jakiego formatu należy używać do wysyłania za pośrednictwem drutu?
— Paweł Szczur,

1
Wierzę, że Oracle poświęciło ponownie, w tym Java Money w Javie 9. Naprawdę szkoda. Ale świetna odpowiedź. Nadal możemy go używać z Maven
— borjab

3
Czy masz źródło, na które Oracle decyduje się nie uwzględniać Java Money w Javie 9?
— Abdull

26

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.


Należy pamiętać, że ilość pieniędzy może przepełnić rozmiar int
— eversor

5
@eversor, który potrzebowałby ponad 20 milionów dolarów, większość aplikacji nie potrzebowałaby tak wiele, gdyby działały długo, ponieważ nawet nasze szopy nie obsługują wystarczającej ilości pieniędzy, aby to przelać
— maniak ratchet

4
@ratchetfreak Prawdopodobnie lepiej użyć dłuższego czasu.
— trognanders

4
Wiele banków obsługuje codziennie znacznie większe sumy pieniędzy, które wynoszą 20 000 000 USD. To nawet nie bierze pod uwagę walut takich jak jen z dużymi kursami wymiany do dolara. Typy liczb całkowitych mogą być najlepsze, aby uniknąć problemów z zaokrąglaniem, chociaż stają się one nieczytelne przy obliczaniu odsetek i kursów walut. Jednak w zależności od aplikacji może być wymagana 64-bitowa liczba całkowita.
— Alchymist

Idealnie byłoby, gdyby mikrodolary działały tak, jakbyś robił np. 10 USD / 3, błąd zaokrąglania (3333.3 => 3333.0) nie wpływa tak bardzo na końcową wartość (w tym przypadku w ogóle nie wpływa na rzeczywistą wartość, chociaż jest niebezpieczne jest założenie, że nigdy nie będzie). Jest to szczególnie ważne, jeśli wykonujesz wiele obliczeń z rzędu, zanim użytkownik zobaczy wynik, ponieważ błędy zaokrąglania będą się komplikować.
— Chris Browne


11

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:

  1. Interfejs API do obsługi np. Kwot pieniężnych i walut
  2. Interfejsy API do obsługi wymiennych implementacji
  3. Fabryki do tworzenia instancji klas implementacji
  4. Funkcjonalność do obliczeń, konwersji i formatowania kwot pieniężnych
  5. Java API do pracy z pieniędzmi i walutami, które planuje się włączyć do Java 9.
  6. 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


Wszystko to jest miłe, ale jak sugeruje Federico powyżej, wygląda wolniej niż BigDecimal :-)) tylko zły żart, ale dam temu test już rok później ...
— kensai

6

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.



6

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


Co ciekawe, przeprowadzę ten sam test z najnowszymi materiałami na JDK9
— kensai,

4

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)


3

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.


2

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.


6
Chociaż ja też lubię małe typy, nigdy nie powinieneś używać podwójnego do przechowywania wartości pieniężnej.
— orien
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.