使用BigDecimal处理货币

时间:2009-08-31 23:14:30

标签: java currency bigdecimal

我试图使用longs为自己的类创建自己的类,但显然我应该使用BigDecimal代替。有人可以帮助我开始吗?将BigDecimal用于美元货币的最佳方式是什么,例如至少为美分货币,但不超过2个小数点等等。BigDecimal的API很大,我不喜欢不知道使用哪种方法。此外,BigDecimal具有更高的精确度,但如果它通过double并不是全部丢失了吗?如果我执行新的BigDecimal(24.99),它与使用double有什么不同?或者我应该使用使用String的构造函数吗?

9 个答案:

答案 0 :(得分:73)

以下是一些提示:

  1. 如果您需要提供的精度,请使用BigDecimal进行计算(Money值通常需要此值)。
  2. 使用NumberFormat类进行显示。本课程将处理不同货币金额的本地化问题。但是,它只会接受基元;因此,如果您因为转换为double而接受准确性的微小变化,则可以使用此类。
  3. 使用NumberFormat类时,请使用scale()实例上的BigDecimal方法设置精度和舍入方法。
  4. PS:如果你想知道,BigDecimal is always better than double, when you have to represent money values in Java

    PPS:

    创建BigDecimal个实例

    BigDecimal provides constructors to take in primitive valuesString对象以来,这非常简单。您可以使用preferably the one taking the String object。例如,

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

答案 1 :(得分:33)

我建议对Money Pattern进行一些研究。 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);

答案 2 :(得分:7)

或者,等待JSR-354。 Java Money and Currency API即将推出!

答案 3 :(得分:1)

1)如果您被限制在double精度,使用BigDecimal的一个原因是使用从BigDecimal创建的double来实现操作。

2)BigDecimal由任意精度整数非标定值和非负32位整数标度组成,而double包含对象中基本类型double的值。类型为Double的对象包含单个字段,其类型为double

3)它应该没有区别

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

答案 4 :(得分:1)

原始数字类型对于在内存中存储单个值很有用。但是当使用double和float类型处理计算时,舍入存在问题。因为内存表示不能精确映射到值,所以会发生这种情况。例如,double值应该占用64位,但Java不使用所有64位。它只存储它认为数字的重要部分。因此,当您将float或double类型的值一起添加时,可能会出现错误的值。

请参阅短片https://youtu.be/EXxUSz9x7BM

答案 5 :(得分:0)

如果要将美分舍入到2个小数点,请使用BigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP)。但是,在进行计算时请注意舍入错误。当你将货币价值四舍五入时,你需要保持一致。在完成所有计算后,在最后进行四舍五入,或者在进行任何计算之前对每个值应用舍入。使用哪一个取决于您的业务需求,但一般来说,我认为在最后进行四舍五入似乎对我更有意义。

在构建String货币值时使用BigDecimal。如果使用double,则最后会有一个尾随浮点值。这是由于计算机架构有关double / float值以二进制格式表示的方式。

答案 6 :(得分:0)

有一个关于如何在javapractices.com上执行此操作的广泛示例。特别参见Money类,它旨在使货币计算比直接使用BigDecimal更简单。

Money类的设计旨在使表达式更自然。例如:

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

WEB4J工具有一个类似的类Decimal,它比Money类更精致。

答案 7 :(得分:0)

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

答案 8 :(得分:0)

我会很激进。没有BigDecimal。

这是一篇很棒的文章 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

可能的解决方案吗?

我附带了一个-使用String将Java中的BigDecimal作为 String 存储到数据库中。您已经进行了验证,例如@NotNull@Min(10)等。然后可以在更新或保存时使用触发器,以检查当前字符串是否是您需要的数字。虽然没有mongo的触发器。 Is there a built-in way for Mongodb trigger function calls?

我在玩耍时有一个缺点-BigDecimal as String in Swagger defenition

我需要招摇摇晃,所以我们的前端团队知道我给他们传递了一个以String表示的数字。例如, DateTime 以字符串形式显示。

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

  

一个标准的长值可以存储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