ข้อมูลชนิดใดที่ใช้เป็นเงินใน Java [ปิด]


183

ข้อมูลประเภทใดที่คุณควรใช้เป็นเงินใน Java


2
ขึ้นอยู่กับว่าคุณกำลังจะทำอะไร กรุณาเสนอข้อมูลเพิ่มเติม
eversor

@eversor คุณช่วยให้ฉันอธิบายชนิดของข้อมูลที่ควรใช้สำหรับการดำเนินการที่แตกต่างกันได้หรือไม่?
questborn

1
ฉันกำลังทำการคำนวณซึ่งทำให้ฉันต้องเป็นตัวแทนเซ็นต์อย่างถูกต้อง
questborn

คุณสามารถบอกจำนวนเงินที่ใหญ่ที่สุดที่แอพของคุณจะต้องจัดการหรือไม่? และการคำนวณของคุณพวกเขาจะเป็นเรื่องง่าย (aditions ฯลฯ ) หรือการดำเนินงานทางการเงินที่ซับซ้อนมากขึ้น?
eversor

คำตอบ:


133

Java มีCurrencyคลาสที่แสดงรหัสสกุลเงิน ISO 4217 BigDecimalเป็นประเภทที่ดีที่สุดสำหรับการแทนค่าทศนิยมสกุลเงิน

Joda Moneyได้จัดหาห้องสมุดเพื่อเป็นตัวแทนของเงิน


5
ทำไมเราไม่ใช้ float หรือ double แทน?
Erran Morad

20
@Borat Sagdiyev นี่คือเหตุผลว่าทำไม นอกจากนี้คุณยังสามารถอ้างถึงนี้
Buhake Sindi

2
@ Borat: ถ้าคุณรู้ว่าคุณกำลังทำอะไรให้ดูบทความนี้โดย Peter Lawrey แต่ดูเหมือนว่าเป็นเรื่องใหญ่ที่ยุ่งยากในการปัดเศษทั้งหมดเพื่อใช้ BigDecimals
Nathan Hughes

35
"ถ้าฉันมีค่าเล็กน้อยทุกครั้งที่ฉันเห็นใครบางคนใช้ FLOAT เพื่อเก็บสกุลเงินฉันจะได้ $ 999.997634" - Bill Karwin
Collin Krawll

36

คุณสามารถใช้Money and Currency API (JSR 354)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 หรือไม่?
Abdull

25

ประเภทหนึ่งที่แสดงถึงค่าที่น้อยที่สุดที่เป็นไปได้ กล่าวอีกนัยหนึ่งโปรแกรมของคุณควรคิดเป็นเซ็นต์ไม่ใช่ดอลลาร์ / ยูโร

สิ่งนี้ไม่ควรหยุดคุณจากการให้ gui แปลกลับไปเป็นดอลลาร์ / ยูโร


จำไว้ว่าจำนวนเงินที่สามารถล้นขนาดของ int
eversor

5
@eversor ที่ต้องการมากกว่า 20 ล้านดอลลาร์แอปส่วนใหญ่ไม่ต้องการมากนักถ้าพวกเขาทำนานจะเพียงพอเพราะแม้แต่กติกาของเราก็จัดการกับเพื่อนที่มากเกินพอที่จะทำเช่นนั้น
วงล้อประหลาด

4
@ ratchetfreak น่าจะดีกว่าถ้าใช้นาน ๆ
trognanders

5
ธนาคารหลายแห่งจัดการเงินก้อนใหญ่กว่านั้นได้ $ 20,000,000 ทุกวัน สิ่งนี้ไม่ได้คำนึงถึงสกุลเงินเช่นเยนที่มีอัตราแลกเปลี่ยนขนาดใหญ่เป็นดอลลาร์ ประเภทจำนวนเต็มอาจดีที่สุดในการหลีกเลี่ยงปัญหาการปัดเศษแม้ว่าพวกเขาจะยุ่งกับการคำนวณดอกเบี้ยและอัตราแลกเปลี่ยน อย่างไรก็ตามคุณอาจต้องใช้ชนิดจำนวนเต็ม 64 บิต
เล่นแร่แปรธาตุ

microdollars ในอุดมคติแล้วถ้าคุณทำเช่น $ 10/3 ข้อผิดพลาดในการปัดเศษ (3333.3 => 3333.0) จะไม่ส่งผลกระทบต่อค่าสุดท้ายเท่าไหร่ (ในกรณีนี้มันจะไม่ส่งผลกระทบต่อมูลค่าที่แท้จริงเลยแม้ว่าจะเป็น อันตรายที่จะคิดว่ามันจะไม่เป็น) สิ่งนี้สำคัญอย่างยิ่งหากคุณทำการคำนวณเป็นจำนวนมากในแถวก่อนที่ผู้ใช้จะเห็นผลลัพธ์เนื่องจากข้อผิดพลาดในการปัดเศษจะรวมกัน
Chris Browne


11

JSR 354: API เงินและสกุลเงิน

JSR 354 จัดทำ API สำหรับการเป็นตัวแทนการขนส่งและการคำนวณที่ครอบคลุมด้วยเงินและสกุลเงิน คุณสามารถดาวน์โหลดได้จากลิงค์นี้:

JSR 354: การดาวน์โหลด API เงินและสกุลเงิน

ข้อมูลจำเพาะประกอบด้วยสิ่งต่าง ๆ ดังต่อไปนี้:

  1. API สำหรับจัดการเช่นจำนวนเงินและสกุลเงิน
  2. API เพื่อสนับสนุนการใช้งานที่สามารถเปลี่ยนแทนกันได้
  3. โรงงานสำหรับสร้างอินสแตนซ์ของคลาสการใช้งาน
  4. ฟังก์ชันการคำนวณการแปลงและการจัดรูปแบบของจำนวนเงิน
  5. Java API สำหรับการทำงานกับ Money และ Currencies ซึ่งมีการวางแผนที่จะรวมอยู่ใน 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;

MonetaryAmounts สามารถปัดเศษโดยใช้ตัวดำเนินการปัดเศษ:

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

ค้นหา Java 9 Money และ Currency API (JSR 354)

ดูเพิ่มเติมที่: JSR 354 - สกุลเงินและเงิน


ทั้งหมดนี้เป็นสิ่งที่ดี แต่ตามที่ Federico แนะนำไว้ข้างต้นมันดูช้ากว่า BigDecimal :-)) เรื่องตลกไม่ดีเท่านั้น แต่ฉันจะให้ทดสอบตอนนี้ 1 ปีต่อมา ...
kensai

6

คุณควรใช้BigDecimal เพื่อเป็นตัวแทนของค่าเงินมันอนุญาตให้คุณใช้โหมดการปัดเศษที่หลากหลายและในแอปพลิเคชันทางการเงินโหมดการปัดเศษมักเป็นข้อกำหนดที่ยากมากที่อาจได้รับคำสั่งจากกฎหมาย



6

ฉันได้ทำ microbenchmark (JMH) เพื่อเปรียบเทียบ Moneta (การใช้สกุลเงิน Java JSR 354) กับ BigDecimal ในแง่ของประสิทธิภาพ

น่าแปลกที่ประสิทธิภาพ BigDecimal น่าจะดีกว่าของ moneta ฉันใช้ moneta config ต่อไปนี้:

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

สำหรับกรณีที่ง่าย (สกุลเงิน) it'is พอ/Integer Longเก็บเงินเป็นเซ็นต์ (... ) หรือหนึ่งในพันต่อเซนต์ (ความแม่นยำที่คุณต้องการด้วยตัวแบ่งคงที่)


3

BigDecimal เป็นประเภทข้อมูลที่ดีที่สุดสำหรับการใช้สกุลเงิน

มีหลายคอนเทนเนอร์สำหรับสกุลเงิน แต่ทั้งหมดใช้ BigDecimal เป็นชนิดข้อมูลพื้นฐาน คุณจะไม่ผิดกับ BigDecimal อาจใช้ BigDecimal.ROUND_HALF_EVEN ปัดเศษ


2

ฉันชอบใช้Tiny Typesซึ่งจะห่อทั้งสองครั้ง BigDecimal หรือ int ตามคำตอบก่อนหน้านี้ได้แนะนำ (ฉันจะใช้สองครั้งเว้นแต่จะมีปัญหาความแม่นยำครอบตัดขึ้น)

ประเภทเล็ก ๆ ให้ความปลอดภัยแก่คุณดังนั้นคุณจะไม่สับสนว่าจะได้เงินสองเท่ากับคู่อื่น ๆ


6
ในขณะที่ฉันชอบประเภทเล็ก ๆ คุณไม่ควรใช้สองครั้งเพื่อเก็บค่าเงิน
orien
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.