2011-11-16 9 views
113

Jaki jest najlepszy typ danych do wykorzystania dla pieniędzy w aplikacji Java?Jaki jest najlepszy typ danych, jakich należy używać dla pieniędzy w aplikacji Java?

+2

Zależy od tego, jakie operacje zamierzasz wykonać. Proszę podać więcej informacji. – eversor

+0

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

+1

Wykonuję obliczenia, które wymagają dokładnego przedstawienia centów. – questborn

Odpowiedz

94

Java ma Currency klasę, która reprezentuje ISO 4217 kody walut. BigDecimal to najlepszy typ do reprezentowania wartości dziesiętnych w walutach.

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

+2

Dlaczego nie możemy zamiast tego używać float lub double? –

+12

@Borat Sagdiyev [To jest powód] (http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems). Możesz również skorzystać z [this] (https://www.securecoding.cert.org/confluence/display/java/NUM04-J.+Do+not+use+useingpointpoint+numbers+if+precise+computation + jest + wymagany). –

+2

@Borat: możesz, jeśli wiesz, co robisz, przeczytaj [ten artykuł] (http://vanillajava.blogspot.com/2011/08/double-your-money-again.html) Petera Lawrey'a. ale wydaje się co najmniej kłopotliwe, aby zrobić wszystkie zaokrąglenia, aby korzystać z BigDecimals. –

5

użyłbym Joda Money

To wciąż w wersji 0.6, ale wygląda bardzo obiecująco

1

BigDecimal jest najlepszy typ danych do wykorzystania na waluty.

Istnieje wiele kontenerów na waluty, ale wszystkie one używają BigDecimal jako podstawowego typu danych. Nie popełnisz błędu w BigDecimal, prawdopodobnie za pomocą zaokrąglania BigDecimal.ROUND_HALF_EVEN.

2

Należy użyć BigDecimal do reprezentowania wartości pieniężne .To pozwala na korzystanie z wielu trybówzaokrągleń, aw aplikacji finansowych, tryb zaokrąglania jest często trudne wymaganie , które mogą być nawet nakazane przez prawo .

14

Typ integralny reprezentujący najmniejszą możliwą wartość. Innymi słowy, twój program powinien myśleć w centach nie w dolarach/euro.

Nie powinno to powstrzymać cię przed tłumaczeniem gui z powrotem na dolary/euro.

+0

Pamiętaj, że ilość pieniędzy może przekroczyć rozmiar int – eversor

+4

@eversor, który potrzebowałby ponad 20 milionów dolarów, a większość aplikacji nie potrzebowałaby tak dużo, jeśli robią wystarczająco długo, ponieważ nawet nasze produkty nie wystarczą na tyle przepełnienie to –

+2

@ratchetfreak Prawdopodobnie lepiej użyć długiego czasu. –

1

Lubię używać Tiny Types, które będzie zawijać podwójne, BigDecimal lub int, jak sugerowały poprzednie odpowiedzi. (Chciałbym użyć podwójnego, chyba że pojawią się problemy z precyzją).

Tiny Type zapewnia bezpieczeństwo typu, dzięki czemu nie można pomylić podwójnych pieniędzy z innymi graczami.

+5

Podczas gdy ja też lubię małe typy, powinieneś * Nigdy * używać podwójnego do przechowywania wartości pieniężnej. – orien

23

Możesz użyć Money and Currency API (JSR 354). Oczekuje się, że ten interfejs API będzie częścią środowiska Java 9. Możesz używać tego interfejsu API w języku Java 7 i Java 8, o ile dodasz odpowiednie zależności do projektu.

Dla Java 8, dodaj następującą implementację referencyjną jako zależność do swojego pom.xml:

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

Ta zależność będzie przechodni dodać javax.money:money-api jako zależność.

Następnie można użyć 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")); 


    } 
} 
+0

A co z serializacją i zapisywaniem w db? Jakiego formatu należy użyć do przesyłania przez przewód? –

+0

Wierzę, że Oracle odliczył againts, w tym Java Money w Javie 9. Naprawdę wstyd. Ale wspaniała odpowiedź. Nadal możemy używać go z Mavenem – borjab

+1

Czy masz źródło dla Oracle decydujące przeciwko włączeniu Java Money w Java 9? – Abdull

2

Zrobiłem microbenchmark (JMH) porównać Moneta (Java waluty JSR 354 realizacji) przed BigDecimal pod względem wydajności.

Co zaskakujące, wydajność BigDecimal wydaje się lepsza niż moneta. I stosuje się następujące moneta konfiguracji:

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

, w wyniku

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 mnie poprawić, jeśli jestem czegoś brakuje

+0

Interesujące, będę prowadził ten sam test z najnowszymi rzeczami na JDK9 – kensai

7

JSR 354: pieniądze i Waluta API

JSR 354 zapewnia interfejs API do reprezentowania, transportu i wykonywania kompleksowych obliczeń za pomocą pieniędzy i waluty. Można go pobrać z tego linku:

JSR 354: Money and Currency API Download

Specyfikacja składa się z następujących rzeczy:

  1. API do obsługi e. sol. kwoty pieniężne i waluty
  2. Apis wspierać wymiennymi realizacje
  3. Fabryki do tworzenia instancji klas realizacji
  4. funkcjonalności do obliczeń, konwersji i formatowania kwot pieniężnych
  5. Java API do pracy z pieniędzmi i waluty, która jest planowane do uwzględnienia w Javie 9.
  6. Wszystkie klasy specyfikacji i interfejsy znajdują się w pakiecie javax.money. *.

Przykładowe Przykłady JSR 354: Pieniądze i Waluta API:

Przykładem tworzenia MONETARYAMOUNT i drukowanie go do 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)); 

When przy użyciu referencyjnego 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)); 

API obsługuje również obliczeń z 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 pozwalają dostępem przypisaną walutę, kwotę numeryczną, jego precyzja i więcej:

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; 

MonetaryAmounts można zaokrąglać za pomocą operatora zaokrąglającego:

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ępne są niektóre przydatne metody filtrowania, sortowania i grupowania.

operacje
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")); 

klienta 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:

Handling money and currencies in Java with JSR 354

Looking into the Java 9 Money and Currency API (JSR 354)

Zobacz także: JSR 354 - Currency and Money

+0

Wszystko to jest miłe, ale jak sugerował Federico powyżej, wygląda na wolniejsze niż BigDecimal :-)), tylko jeden zły dowcip, ale dam mu test teraz 1 rok później. .. – kensai

1

W przypadku prostego przypadku (jedna waluta) wystarczy Integer/Long. Przechowuj pieniądze w centach (...) lub setnych/tysięcznych centów (każda precyzja, jakiej potrzebujesz, ze stałym rozdzielaczem)

Powiązane problemy