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?

Questborn
źródło
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.

Buhake Sindi
źródło
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") );
    }
}
Abdull
źródło
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.

maniak zapadkowy
źródło
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

Affy
źródło
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.

Sandeep Pathak
źródło
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

Federico Gaule Palombarani
źródło
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)

Grigorij Kislin
źródło
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.

Anthony Blake
źródło
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.

Garrett Smith
źródło
6
Chociaż ja też lubię małe typy, nigdy nie powinieneś używać podwójnego do przechowywania wartości pieniężnej.
orien