使用 BigDecimal 处理货币

我试图为使用多头的货币创建自己的类,但显然我应该使用 BigDecimal。有人能帮我开始吗?对于美元货币,使用 BigDecimal的最佳方法是什么,比如将美分至少设置为小数点后两位,等等。BigDecimal的 API 非常庞大,我不知道该使用哪些方法。而且,BigDecimal有更好的精度,但是如果它通过 double,不就全部丢失了吗?如果我做新的 BigDecimal(24.99),它将如何不同于使用 double?还是应该使用使用 String的构造函数?

128713 次浏览

1)如果仅限于 double的精度,使用 BigDecimal的一个原因是使用从 double创建的 BigDecimal实现操作。

2) BigDecimal由一个任意精度的整数未缩放值和一个非负的32位整数缩放值组成,而双精度值在对象中包装一个基元类型 double的值。类型为 Double的对象包含一个类型为 double的字段

3)这应该没有什么区别

使用 $和精度应该没有困难,一种方法是使用 System.out.printf

以下是一些提示:

  1. 如果您需要 BigDecimal提供的精度,可以使用 BigDecimal进行计算(货币值通常需要这一点)。
  2. 使用 NumberFormat类进行显示。这个类将处理不同货币金额的本地化问题。但是,它将只接受原语; 因此,如果您能够接受由于转换为 double而导致的精度上的小变化,那么您可以使用这个类。
  3. 当使用 NumberFormat类时,在 BigDecimal实例上使用 scale()方法来设置精度和舍入方法。

附言: 如果你想知道的话,当您必须用 Java 表示货币值时,ABC0总是优于 double

附注:

创建 BigDecimal实例

这是相当简单的,因为 BigDecimal提供构造函数来接受基元值,和 String对象。你可以使用这些,最好是带有 String对象的那个。例如,

BigDecimal modelVal = new BigDecimal("24.455");
BigDecimal displayVal = modelVal.setScale(2, RoundingMode.HALF_EVEN);

显示 BigDecimal实例

您可以使用 setMinimumFractionDigitssetMaximumFractionDigits方法调用来限制显示的数据量。

NumberFormat usdCostFormat = NumberFormat.getCurrencyInstance(Locale.US);
usdCostFormat.setMinimumFractionDigits( 1 );
usdCostFormat.setMaximumFractionDigits( 2 );
System.out.println( usdCostFormat.format(displayVal.doubleValue()) );

使用 BigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP)时,你想四舍五入到2小数点为分。但是在进行计算时要注意四舍五入误差。你需要一致的时候,你将做四舍五入的货币价值。在所有计算完成之后,要么在结束时进行一次舍入,要么在进行任何计算之前对每个值进行舍入。使用哪一个取决于您的业务需求,但是一般来说,我认为在最后进行四舍五入似乎对我来说更有意义。

当你为货币价值构造 BigDecimal时使用 String。如果使用 double,它的末尾将有一个浮点值。这是由于计算机体系结构关于 double/float值如何以二进制格式表示。

有一个广泛的例子来说明如何在 javapractices.com 上做到这一点。请特别参见 Money类,它意在使货币计算比直接使用 BigDecimal更简单。

这个 Money类的设计是为了使表达式更自然。例如:

if ( amount.lt(hundred) ) {
cost = amount.times(price);
}

WEB4J 工具有一个类似的类,称为 Decimal,比 Money类稍微精致一些。

我会推荐一些关于货币模式的研究。Martin Fowler 在他的书《分析模式》中更详细地阐述了这一点。

public class Money {


private static final Currency USD = Currency.getInstance("USD");
private static final RoundingMode DEFAULT_ROUNDING = RoundingMode.HALF_EVEN;


private final BigDecimal amount;
private final Currency currency;


public static Money dollars(BigDecimal amount) {
return new Money(amount, USD);
}


Money(BigDecimal amount, Currency currency) {
this(amount, currency, DEFAULT_ROUNDING);
}


Money(BigDecimal amount, Currency currency, RoundingMode rounding) {
this.currency = currency;
this.amount = amount.setScale(currency.getDefaultFractionDigits(), rounding);
}


public BigDecimal getAmount() {
return amount;
}


public Currency getCurrency() {
return currency;
}


@Override
public String toString() {
return getCurrency().getSymbol() + " " + getAmount();
}


public String toString(Locale locale) {
return getCurrency().getSymbol(locale) + " " + getAmount();
}
}

说到用法:

您将使用 Money对象而不是 BigDecimal来表示所有的钱。将钱表示为大小数将意味着您将有格式化的钱在您显示它的每一个地方。想象一下如果显示标准发生变化。你得到处编辑。相反,您可以使用 Money模式将货币格式集中到一个单独的位置。

Money price = Money.dollars(38.28);
System.out.println(price);

或者,等待 JSR-354.Java 货币和货币 API 即将到来!

基元数值类型对于在内存中存储单个值非常有用。但是当处理使用 double 和 float 类型的计算时,舍入会出现问题,这是因为内存表示不能精确地映射到值。例如,一个双精度值应该是64位的,但是 Java 并没有全部使用64位,它只是存储了它认为数字的重要部分。因此,当将 float 或 double 类型的值相加时,可能会得到错误的值。

请看一个短片 https://youtu.be/EXxUSz9x7BM

NumberFormat.getNumberInstance(java.util.Locale.US).format(num);

我会很激进,不要大十进制。

这是一篇很棒的文章 Https://lemnik.wordpress.com/2011/03/25/bigdecimal-and-your-money/

从这里开始。

import java.math.BigDecimal;


public class Main {


public static void main(String[] args) {
testConstructors();
testEqualsAndCompare();
testArithmetic();
}


private static void testEqualsAndCompare() {
final BigDecimal zero = new BigDecimal("0.0");
final BigDecimal zerozero = new BigDecimal("0.00");


boolean zerosAreEqual = zero.equals(zerozero);
boolean zerosAreEqual2 = zerozero.equals(zero);


System.out.println("zerosAreEqual: " + zerosAreEqual + " " + zerosAreEqual2);


int zerosCompare = zero.compareTo(zerozero);
int zerosCompare2 = zerozero.compareTo(zero);
System.out.println("zerosCompare: " + zerosCompare + " " + zerosCompare2);
}


private static void testArithmetic() {
try {
BigDecimal value = new BigDecimal(1);
value = value.divide(new BigDecimal(3));
System.out.println(value);
} catch (ArithmeticException e) {
System.out.println("Failed to devide. " + e.getMessage());
}
}


private static void testConstructors() {
double doubleValue = 35.7;
BigDecimal fromDouble = new BigDecimal(doubleValue);
BigDecimal fromString = new BigDecimal("35.7");


boolean decimalsEqual = fromDouble.equals(fromString);
boolean decimalsEqual2 = fromString.equals(fromDouble);


System.out.println("From double: " + fromDouble);
System.out.println("decimalsEqual: " + decimalsEqual + " " + decimalsEqual2);
}
}

上面有指纹

From double: 35.7000000000000028421709430404007434844970703125
decimalsEqual: false false
zerosAreEqual: false false
zerosCompare: 0 0
Failed to devide. Non-terminating decimal expansion; no exact representable decimal result.

将 BigDecimal 存储到数据库中如何?该死,它还可以存储双倍价值?至少,如果我在没有任何高级配置的情况下使用 mongoDb,它将把 BigDecimal.TEN存储为 1E1

可能的解决方案?

I came with one - use String to store BigDecimal in Java as a 绳子 into the database. You have validation, for example @NotNull, @Min(10), etc... Then you can use a trigger on update or save to check if current string is a number you need. There are no triggers for mongo though. 是否有一种内置的方法用于调用 Mongodb 触发器函数?

有一个缺点,我正在享受周围的乐趣-在 Swagger 定义中将 BigDecimal 作为字符串

我需要引起他们的注意这样我们的前端团队就能理解我传递给他们一个字符串形式的数字。例如,日期时间表示为 String。

我在上面的文章中读到了另一个很酷的解决方案..。 使用 很长存储精确的数字。

一个标准的长期价值可以将 Unites States 国债(以美分而非美元计)的当前价值存储6477倍,而不会溢出。更重要的是: 它是一个整数类型,而不是一个浮点数。这使得它更容易和准确的工作,并保证行为。


更新

Https://stackoverflow.com/a/27978223/4587961

也许将来 MongoDb 会增加对 BigDecimal 的支持。 Https://jira.mongodb.org/browse/server-1393 3.3.8似乎已经做到了这一点。

这是第二种方法的一个例子。使用缩放。 Http://www.technology-ebay.de/the-teams/mobile-de/blog/mapping-bigdecimals-with-morphia-for-mongodb.html