Який тип даних використовувати для грошей на Java? [зачинено]


183

Який тип даних слід використовувати для грошей на Java?


2
Це залежить від того, які операції ви збираєтеся робити. Будь ласка, запропонуйте більше інформації.
eversor

@eversor Чи можете ви описати, який тип даних слід використовувати для різних операцій?
квестор

1
Я роблю розрахунки, які вимагають, щоб я точно представляв центи.
квестор

Чи можете ви передбачити найбільшу суму грошей, яку вам доведеться обробити вашому додатку? І, ваші розрахунки, чи будуть вони простими (оголошення тощо) або складнішими фінансовими операціями?
eversor

Відповіді:


133

У Java є Currencyклас, який представляє коди валюти ISO 4217. BigDecimal- найкращий тип представлення десяткових значень валюти.

Joda Money забезпечив бібліотеку для представлення грошей.


5
Чому ми не можемо використовувати float або double натомість?
Ерран Морад

20
@Borat Sagdiyev Це причина . Також ви можете посилатися на це .
Бухаке Сінді

2
@Borat: якщо ти знаєш, що ти робиш, ти можеш переглянути цю статтю Пітера Лорі. але, здається, принаймні настільки клопоту зробити все округлення, як використовувати BigDecimals.
Натан Х'юз

35
"Якби я мав кайфу кожен раз, коли я бачив, як хтось використовує FLOAT для зберігання валюти, я мав би $ 999.997634" - Білл Карвін
Колін Краулл

36

Ви можете використовувати API Money і Currency (JSR 354) . Ви можете використовувати цей API, якщо ви додасте відповідні залежності до свого проекту.

Для Java 8 додайте наступну посилання на реалізацію як залежність до свого pom.xml:

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

Ця залежність буде транзитивно додаватися javax.money:money-apiяк залежність.

Потім можна використовувати 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") );
    }
}

А як щодо серіалізації та збереження в db? Який формат слід використовувати для надсилання по дроту?
Paweł Szczur

1
Я вважаю, що Oracle виділив гроші, включаючи Java Money на Java 9. Дійсно ганьба. Але чудова відповідь. Ми все ще можемо використовувати його з Maven
borjab

3
Чи є у вас джерело для рішення Oracle проти включення Java Money в Java 9?
Абдулл

26

Цілісний тип, що представляє найменше можливе значення. Іншими словами, ваша програма повинна думати в копіях, а не в доларах / євро.

Це не повинно зупинити вас від того, щоб гуї перевели його назад у долари / євро.


Майте на увазі , що сума грошей може переповнити розмір міжнар
eversor

5
@eversor, на який знадобиться понад 20 мільйонів доларів, більшості додатків не знадобиться стільки, якщо вони будуть тривалими, буде достатньо, бо навіть наші уряди не впораються з достатньою грошима, щоб переповнити це
храповий вирод

4
@ratchetfreak Напевно, краще використовувати довгий тоді.
трогнандри

4
Багато банків обробляють набагато більші суми грошей, які щодня становлять 20 000 000 доларів. Це навіть не враховує таких валют, як ієна з великими обмінними курсами до долара. Типи цілих категорій можуть бути найкращими, щоб уникнути проблем із округленням, хоча вони заплуталися з розрахунками відсотків та обмінного курсу. Однак, залежно від програми, можливо, вам знадобиться 64-бітний цілочисельний тип.
Алхімік

В ідеалі, мікродоларів, насправді, як тоді, якщо ви робите, наприклад, $ 10/3, то помилка округлення (3333,3 => 3333,0) не впливає на кінцеве значення настільки сильно (в цьому випадку це взагалі не впливає на реальне значення, хоча це небезпечно вважати, що цього ніколи не буде). Це особливо важливо, якщо ви робите багато обчислень поспіль, перш ніж ваш користувач побачить результат, оскільки помилки округлення посиляться.
Кріс Браун


11

JSR 354: API і гроші та валюти

JSR 354 надає API для представлення, транспортування та проведення комплексних розрахунків за допомогою грошей і валюти. Ви можете завантажити його за посиланням:

JSR 354: Завантажити API грошей та валюти

Специфікація складається з наступних речей:

  1. API для обробки, наприклад, грошових сум та валют
  2. API для підтримки взаємозамінних реалізацій
  3. Фабрики для створення екземплярів класів реалізації
  4. Функціональність для розрахунків, перерахунку та форматування грошових сум
  5. Java API для роботи з грошима та валютами, який планується включити в Java 9.
  6. Усі класи специфікацій та інтерфейси розміщені в пакеті javax.money. *.

Приклади прикладів JSR 354: API грошей та валюти:

Приклад створення MonetaryAmount та друку на консолі виглядає приблизно так:

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

Використовуючи API реалізації посилань, необхідний код набагато простіший:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

API також підтримує розрахунки з MonetaryAmounts:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));

ВалютаУніта та Грошова сума

// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);

MonetaryAmount має різні методи, що дозволяють отримати доступ до призначеної валюти, числову суму, її точність та багато іншого:

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;

Грошові суми можна округлити за допомогою оператора округлення:

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

Під час роботи з колекціями MonetaryAmounts доступні деякі приємні корисні методи фільтрації, сортування та групування.

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

Спеціальні операції 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

Ресурси:

Обробка грошей і валют на Java за допомогою JSR 354

Переглядаючи API 9 грошей та валюти Java 9 (JSR 354)

Див. Також: JSR 354 - Валюта та гроші


Все це приємно, але як запропонував Федеріко вище, це виглядає повільніше, ніж BigDecimal :-)) тільки жарт, але тільки я тестую його вже через 1 рік ...
kensai

6

Ви повинні використовувати BigDecimal для представлення грошових значень. Це дозволяє використовувати різноманітні режими округлення , а у фінансових додатках режим округлення часто є жорсткою вимогою, яка може бути передбачена законодавством.



6

Я зробив мікро-показник (JMH), щоб порівняти Moneta (реалізація Java-валюти JSR 354) та BigDecimal за показниками продуктивності.

Дивно, але ефективність BigDecimal виявляється кращою, ніж монета. Я використовував таку конфігурацію монети:

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

У результаті

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

Будь ласка, не соромтеся виправити мене, якщо я щось пропускаю


Цікаво, я побіжу ж тест з останнім матеріалом на JDK9
Kensai

4

Для простого випадку (однієї валюти) достатньо Integer/ Long. Зберігайте гроші в центах (...) або сотих / тисячних центах (будь-яка точність, потрібна з фіксованим дільником)


3

BigDecimal - це найкращий тип даних, що використовується для валюти.

Є багато контейнерів для валюти, але всі вони використовують BigDecimal як базовий тип даних. Ви не помилитесь з BigDecimal, ймовірно, використовуючи BigDecimal.ROUND_HALF_EVEN округлення.


2

Мені подобається використовувати крихітні типи, які б обертали або подвійний, або BigDecimal, або int, як пропонували попередні відповіді. (Я використовував би подвійний, якщо не виникнуть проблеми з точністю).

Крихітний тип забезпечує безпеку типу, щоб ви не плутали подвійні гроші з іншими парними.


6
Хоча я теж люблю крихітні типи, ти ніколи не повинен використовувати подвійний, щоб зберігати грошову вартість.
orien
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.