简体   繁体   English

使用BigDecimal处理货币

[英]Using BigDecimal to work with currencies

I was trying to make my own class for currencies using longs, but apparently I should use BigDecimal instead. 我试图为使用longs的货币创建自己的类,但显然我应该使用BigDecimal 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. BigDecimal用于美元货币的最佳方式是什么,比如至少但不超过2美分的小数点等等BigDecimal的API很大,我不知道使用哪种方法。 Also, BigDecimal has better precision, but isn't that all lost if it passes through a double ? 此外, BigDecimal具有更好的精度,但是如果它通过double并不是全部丢失? if I do new BigDecimal(24.99) , how will it be different than using a double ? 如果我做新的BigDecimal(24.99) ,它与使用double什么不同? Or should I use the constructor that uses a String instead? 或者我应该使用使用String的构造函数吗?

Here are a few hints: 以下是一些提示:

  1. Use BigDecimal for computations if you need the precision that it offers (Money values often need this). 如果需要它提供的精度,请使用BigDecimal进行计算(Money值通常需要此值)。
  2. Use the NumberFormat class for display. 使用NumberFormat类进行显示。 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 a double , you could use this class. 因此,如果您因为转换为double而接受准确性的微小变化,则可以使用此类。
  3. When using the NumberFormat class, use the scale() method on the BigDecimal instance to set the precision and the rounding method. 使用NumberFormat类时,请使用BigDecimal实例上的scale()方法设置精度和舍入方法。

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

Creating BigDecimal instances 创建BigDecimal实例

This is fairly simple since BigDecimal provides constructors to take in primitive values , and String objects. 这很简单,因为BigDecimal提供构造函数来接受原始值String对象。 You could use those, preferably the one taking the String object . 您可以使用那些, 最好是使用String对象的那些 For example, 例如,

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. 您可以使用setMinimumFractionDigitssetMaximumFractionDigits方法调用来限制显示的数据量。

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

I would recommend a little research on Money Pattern. 我建议对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 . 您可以使用Money对象而不是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模式,将Money格式集中到一个位置。

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

Or, wait for JSR-354 . 或者,等待JSR-354 Java Money and Currency API coming soon! Java Money和Currency API即将推出!

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的一个原因是用double s创建的BigDecimal实现操作。

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. 2) BigDecimal由一个任意精度整数非标度值和一个非负32位整数标度组成,而double包含一个对象中基本类型double的值。 An object of type Double contains a single field whose type is 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

Use BigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP) when you want to round up to the 2 decimal points for cents. 当你想要舍入到小数的2个小数点时BigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP)请使用BigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP) 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. 哪一个使用将取决于您的业务需求,但一般来说,我认为在最后进行四舍五入似乎对我更有意义。

Use a String when you construct BigDecimal for money value. 在构造BigDecimal以获取货币价值时使用String If you use double , it will have a trailing floating point values at the end. 如果使用double ,则最后会有一个尾随浮点值。 This is due to computer architecture regarding how double / float values are represented in binary format. 这是由于计算机体系结构有关如何以二进制格式表示double / float值。

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. 但是当使用double和float类型处理计算时,舍入存在问题。因为内存表示不能精确映射到值,所以会发生这种情况。 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. 例如,double值应该占用64位,但Java不使用所有64位。它只存储它认为数字的重要部分。 So you can arrive to the wrong values when you adding values together of the float or double type. 因此,当您将float或double类型的值一起添加时,可能会出现错误的值。

Please see a short clip https://youtu.be/EXxUSz9x7BM 请参阅短片https://youtu.be/EXxUSz9x7BM

There is an extensive example of how to do this on javapractices.com. 有一个关于如何在javapractices.com上执行此操作的广泛示例。 See in particular the Money class, which is meant to make monetary calculations simpler than using BigDecimal directly. 特别参见Money类,它的目的是使货币计算比直接使用BigDecimal更简单。

The design of this Money class is intended to make expressions more natural. 这个Money类的设计旨在使表达更自然。 For example: 例如:

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类更加精致。

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

I would be radical. 我会很激进。 No BigDecimal. 没有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? 如何将BigDecimal存储到数据库中? 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 . 至少,如果我在没有任何高级配置的情况下使用mongoDb,它会将BigDecimal.TEN存储为1E1

Possible solutions? 可能的解决方案?

I came with one - use String to store BigDecimal in Java as a String into the database. 我带来了一个使用String来将Java中的BigDecimal作为String存储到数据库中。 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. 您可以验证,例如@NotNull@Min(10)等等......然后你就可以使用触发器在更新或保存检查,如果当前字符串是一个数字,你需要的。 There are no triggers for mongo though. 但是mongo没有触发器。 Is there a built-in way for Mongodb trigger function calls? Mongodb触发器函数调用是否有内置方式?

There is one drawback I am having fun around - BigDecimal as String in Swagger defenition 有一个缺点我玩得很开心 - 在Swagger defenition中BigDecimal为String

I need to generate swagger, so our front-end team understands that I pass them a number presented as a String. 我需要产生招摇,所以我们的前端团队理解我传给他们一个以字符串形式呈现的数字。 DateTime for example presented as a String. DateTime例如以String形式呈现。

There is another cool solution I read in the article above... Use long to 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. 标准长值可以存储美国国债的当前价值(以美分,而不是美元)6477次,没有任何溢出。 Whats more: it's an integer type, not a floating point. 更多:它是一个整数类型,而不是一个浮点。 This makes it easier and accurate to work with, and a guaranteed behavior. 这使得它更容易和准确地使用,并保证行为。


Update 更新

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

Maybe in the future MongoDb will add support for BigDecimal. 也许将来MongoDb会增加对BigDecimal的支持。 https://jira.mongodb.org/browse/SERVER-1393 3.3.8 seems to have this done. https://jira.mongodb.org/browse/SERVER-1393 3.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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM