Java 使用 BigDecimal 处理货币
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1359817/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using BigDecimal to work with currencies
提问by mk12
I was trying to make my own class for currencies using longs, but apparently I should use BigDecimal
instead. Could someone help me get started? What would be the best way to use BigDecimal
s for dollar currencies, like making it at least but no more than 2 decimal places for the cents, etc. The API for BigDecimal
is huge, and I don't know which methods to use. Also, BigDecimal
has better precision, but isn't that all lost if it passes through a double
? if I do new BigDecimal(24.99)
, how will it be different than using a double
? Or should I use the constructor that uses a String
instead?
我试图使用多头为货币创建自己的类,但显然我应该使用它BigDecimal
。有人可以帮我开始吗?将BigDecimal
s 用于美元货币的最佳方法是什么,例如将其设置为至少但不超过 2 个小数位的美分等。APIBigDecimal
很大,我不知道使用哪种方法。此外,BigDecimal
具有更好的精度,但是如果它通过 a ,那不是全部丢失了double
吗?如果我做 new BigDecimal(24.99)
,它与使用 a 有double
什么不同?或者我应该使用使用 a 的构造函数String
?
采纳答案by Vineet Reynolds
Here are a few hints:
这里有一些提示:
- Use
BigDecimal
for computations if you need the precision that it offers (Money values often need this). - Use the
NumberFormat
class for display. This class will take care of localization issues for amounts in different currencies. However, it will take in only primitives; therefore, if you can accept the small change in accuracy due to transformation to adouble
, you could use this class. - When using the
NumberFormat
class, use thescale()
method on theBigDecimal
instance to set the precision and the rounding method.
- 使用
BigDecimal
的计算,如果你需要的精度,它提供了(货币价值往往需要这个)。 - 使用
NumberFormat
该类进行显示。本课程将处理不同货币金额的本地化问题。但是,它只会接收原语;因此,如果您可以接受由于转换为 a 导致的准确性的微小变化double
,则可以使用此类。 - 使用
NumberFormat
类时,使用实例scale()
上的方法BigDecimal
来设置精度和舍入方法。
PS: In case you were wondering, BigDecimal
is always better than double
, when you have to represent money values in Java.
PS:如果您想知道,当您必须在 Java 中表示货币值时,BigDecimal
总是比 更好double
。
PPS:
缴费灵:
Creating BigDecimal
instances
创建BigDecimal
实例
This is fairly simple since BigDecimal
provides constructors to take in primitive values, and String
objects. You could use those, preferably the one taking the String
object. For example,
这相当简单,因为BigDecimal
提供了构造函数来接收原始值和String
对象。您可以使用那些,最好是String
使用object 的那个。例如,
BigDecimal modelVal = new BigDecimal("24.455");
BigDecimal displayVal = modelVal.setScale(2, RoundingMode.HALF_EVEN);
Displaying BigDecimal
instances
显示BigDecimal
实例
You could use the setMinimumFractionDigits
and setMaximumFractionDigits
method calls to restrict the amount of data being displayed.
您可以使用setMinimumFractionDigits
和setMaximumFractionDigits
方法调用来限制显示的数据量。
NumberFormat usdCostFormat = NumberFormat.getCurrencyInstance(Locale.US);
usdCostFormat.setMinimumFractionDigits( 1 );
usdCostFormat.setMaximumFractionDigits( 2 );
System.out.println( usdCostFormat.format(displayVal.doubleValue()) );
回答by Diego Dias
1) If you are limited to the double
precision, one reason to use BigDecimal
s is to realize operations with the BigDecimal
s created from the double
s.
1)如果限于double
精度,使用BigDecimal
s的原因之一是用BigDecimal
s创建的double
s来实现操作。
2) The BigDecimal
consists of an arbitrary precision integer unscaled value and a non-negative 32-bit integer scale, while the double wraps a value of the primitive type double
in an object. An object of type Double
contains a single field whose type is double
2) TheBigDecimal
由一个任意精度的整数非标度值和一个非负的 32 位整数标度组成,而 double 将原始类型的值包装double
在一个对象中。类型对象Double
包含一个字段,其类型为double
3) It should make no difference
3)它应该没有区别
You should have no difficulties with the $ and precision. One way to do it is using System.out.printf
您应该对 $ 和精度没有任何困难。一种方法是使用System.out.printf
回答by tim_wonil
Use BigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP)
when you want to round up to the 2 decimal points for cents. Be aware of rounding off error when you do calculations though. You need to be consistent when you will be doing the rounding of money value. Either do the rounding right at the end just once after all calculations are done, or apply rounding to each value before doing any calculations. Which one to use would depend on your business requirement, but generally, I think doing rounding right at the end seems to make a better sense to me.
使用BigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP)
时要四舍五入到小数点后2分美分。但是,在进行计算时请注意舍入误差。在进行货币价值的四舍五入时,您需要保持一致。要么在所有计算完成后在最后进行一次四舍五入,要么在进行任何计算之前对每个值进行四舍五入。使用哪个取决于您的业务需求,但总的来说,我认为在最后进行四舍五入对我来说似乎更有意义。
Use a String
when you construct BigDecimal
for money value. If you use double
, it will have a trailing floating point values at the end. This is due to computer architecture regarding how double
/float
values are represented in binary format.
String
在构建BigDecimal
货币价值时使用 a 。如果您使用double
,它将在末尾有一个尾随浮点值。这是由于关于double
/float
值如何以二进制格式表示的计算机体系结构。
回答by John O
There is an extensive example of how to do this on javapractices.com. See in particular the Money
class, which is meant to make monetary calculations simpler than using BigDecimal
directly.
javapractices.com 上有一个关于如何执行此操作的广泛示例。特别参见Money
类,它旨在使货币计算比BigDecimal
直接使用更简单。
The design of this Money
class is intended to make expressions more natural. For example:
此类的设计Money
旨在使表达更自然。例如:
if ( amount.lt(hundred) ) {
cost = amount.times(price);
}
The WEB4J tool has a similar class, called Decimal
, which is a bit more polished than the Money
class.
WEB4J 工具有一个类似的类,称为Decimal
,它比Money
类更精致一些。
回答by Brad
I would recommend a little research on Money Pattern. Martin Fowler in his book Analysis pattern has covered this in more detail.
我建议对货币模式进行一些研究。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();
}
}
Coming to the usage:
来到用法:
You would represent all monies using Money
object as opposed to BigDecimal
. Representing money as big decimal will mean that you will have the to format the money every where you display it. Just imagine if the display standard changes. You will have to make the edits all over the place. Instead using the Money
pattern you centralize the formatting of money to a single location.
您将使用Money
object 而不是BigDecimal
. 将货币表示为大十进制意味着您可以在任何显示货币的地方格式化货币。试想一下,如果显示标准发生变化。您将不得不在所有地方进行编辑。而是使用Money
您将货币格式集中到一个位置的模式。
Money price = Money.dollars(38.28);
System.out.println(price);
回答by Victor Grazi
回答by Gregory Nozik
Primitive numeric types are useful for storing single values in memory. But when dealing with calculation using double and float types, there is a problems with the rounding.It happens because memory representation doesn't map exactly to the value. For example, a double value is supposed to take 64 bits but Java doesn't use all 64 bits.It only stores what it thinks the important parts of the number. So you can arrive to the wrong values when you adding values together of the float or double type.
原始数字类型对于在内存中存储单个值很有用。但是在处理使用 double 和 float 类型的计算时,舍入会出现问题。发生这种情况是因为内存表示没有准确映射到值。例如,double 值应该使用 64 位,但 Java 不会使用所有 64 位。它只存储它认为数字的重要部分。因此,当您将 float 或 double 类型的值加在一起时,您可能会得到错误的值。
Please see a short clip https://youtu.be/EXxUSz9x7BM
回答by Heisenberg
NumberFormat.getNumberInstance(java.util.Locale.US).format(num);
回答by Yan Khonski
I would be radical. No BigDecimal.
我会很激进。没有大十进制。
Here is a great article https://lemnik.wordpress.com/2011/03/25/bigdecimal-and-your-money/
这是一篇很棒的文章 https://lemnik.wordpress.com/2011/03/25/bigdecimal-and-your-money/
Ideas from here.
想法来自这里。
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);
}
}
It prints
它打印
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.
How about storing BigDecimal into a database? Hell, it also stores as a double value??? At least, if I use mongoDb without any advanced configuration it will store BigDecimal.TEN
as 1E1
.
如何将 BigDecimal 存储到数据库中?地狱,它也存储为双值???至少,如果我在没有任何高级配置的情况下使用 mongoDb,它将存储BigDecimal.TEN
为1E1
.
Possible solutions?
可能的解决方案?
I came with one - use String to store BigDecimal in Java as a Stringinto 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.
Is there a built-in way for Mongodb trigger function calls?
我带来了一个 - 使用 String 将 Java 中的 BigDecimal 作为字符串存储到数据库中。您有验证,例如@NotNull
,@Min(10)
等...然后您可以在更新或保存时使用触发器来检查当前字符串是否是您需要的数字。不过,mongo 没有触发器。
Mongodb 触发器函数调用是否有内置方法?
There is one drawback I am having fun around - BigDecimal as String in Swagger defenition
我玩得很开心有一个缺点 - BigDecimal as String in Swagger defenition
I need to generate swagger, so our front-end team understands that I pass them a number presented as a String. DateTimefor example presented as a String.
我需要生成 swagger,所以我们的前端团队知道我向他们传递了一个以字符串形式呈现的数字。例如DateTime以字符串形式呈现。
There is another cool solution I read in the article above... Use longto store precise numbers.
我在上面的文章中读到了另一个很酷的解决方案......使用long来存储精确的数字。
A standard long value can store the current value of the Unites States national debt (as cents, not dollars) 6477 times without any overflow. Whats more: it's an integer type, not a floating point. This makes it easier and accurate to work with, and a guaranteed behavior.
一个标准的 long 值可以将美国国债的当前值(以美分,而不是美元)存储 6477 次而不会溢出。更重要的是:它是整数类型,而不是浮点数。这使得使用起来更容易和准确,并保证行为。
Update
更新
https://stackoverflow.com/a/27978223/4587961
https://stackoverflow.com/a/27978223/4587961
Maybe in the future MongoDb will add support for BigDecimal. https://jira.mongodb.org/browse/SERVER-13933.3.8 seems to have this done.
也许在未来 MongoDb 会增加对 BigDecimal 的支持。 https://jira.mongodb.org/browse/SERVER-13933.3.8 似乎做到了这一点。
It is an example of the second approach. Use scaling. http://www.technology-ebay.de/the-teams/mobile-de/blog/mapping-bigdecimals-with-morphia-for-mongodb.html
这是第二种方法的一个例子。使用缩放。 http://www.technology-ebay.de/the-teams/mobile-de/blog/mapping-bigdecimals-with-morphia-for-mongodb.html