72

我试图使用多头来创建自己的货币类,但显然我应该使用它BigDecimal。有人可以帮我开始吗?将BigDecimals 用于美元货币的最佳方法是什么,例如将其设置为至少但不超过 2 个美分小数位等。APIBigDecimal非常庞大,我不知道该使用哪种方法。此外,BigDecimal具有更好的精度,但如果它通过 a 不是全部丢失了double吗?如果我做 new BigDecimal(24.99),它与使用 a 有什么不同double?或者我应该使用使用 a 的构造函数String吗?

4

9 回答 9

88

这里有一些提示:

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

PS:如果您想知道,当您必须在 Java 中表示货币值时,BigDecimal总是比double, 好。

PP:

创建BigDecimal实例

这相当简单,因为BigDecimal提供了构造函数来接收原始值String对象。您可以使用那些,最好是拿走String对象的那个。例如,

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

显示BigDecimal实例

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

NumberFormat usdCostFormat = NumberFormat.getCurrencyInstance(Locale.US);
usdCostFormat.setMinimumFractionDigits( 1 );
usdCostFormat.setMaximumFractionDigits( 2 );
System.out.println( usdCostFormat.format(displayVal.doubleValue()) );
于 2009-08-31T23:45:15.730 回答
40

我建议对金钱模式进行一些研究。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();
    }   
}

来到用法:

您将使用Moneyobject 而不是BigDecimal. 将钱表示为大十进制意味着您可以在显示钱的每个地方格式化钱。试想一下,如果显示标准发生变化。您将不得不在所有地方进行编辑。相反,使用Money您将货币格式集中到一个位置的模式。

Money price = Money.dollars(38.28);
System.out.println(price);
于 2010-03-15T17:59:29.297 回答
11

或者,等待JSR-354。Java 货币和货币 API 即将推出!

于 2013-04-17T21:21:13.903 回答
1

1) 如果您受限于double精度,使用 s 的一个原因是使用从 s 创建的sBigDecimal来实现操作。BigDecimaldouble

2)BigDecimal由任意精度整数未缩放值和非负 32 位整数比例组成,而 double 将原始类型的值包装double在对象中。一个类型的对象Double包含一个类型为double

3)应该没有区别

您应该对 $ 和精度没有任何困难。一种方法是使用System.out.printf

于 2009-08-31T23:33:08.347 回答
1

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

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

于 2009-08-31T23:45:24.280 回答
1

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

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

于 2016-03-01T16:16:20.287 回答
0

javapractices.com 上有一个详细的示例说明如何执行此操作。尤其是这个Money类,它的目的是使货币计算比BigDecimal直接使用更简单。

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

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

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

于 2009-09-01T10:39:46.633 回答
0
NumberFormat.getNumberInstance(java.util.Locale.US).format(num);
于 2016-08-01T23:32:35.310 回答
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.TEN1E1.

可能的解决方案?

我带来了一个 - 使用 String 将 Java 中的 BigDecimal 作为String存储到数据库中。您有验证,例如@NotNull@Min(10)等...然后您可以在更新或保存时使用触发器来检查当前字符串是否是您需要的数字。不过,mongo 没有触发器。 Mongodb触发函数调用有内置方式吗?

我很喜欢一个缺点 - BigDecimal as String in Swagger defenition

我需要生成 swagger,所以我们的前端团队知道我向他们传递了一个以字符串形式呈现的数字。例如,日期时间显示为字符串。

我在上面的文章中读到了另一个很酷的解决方案……使用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

于 2019-02-13T23:26:46.407 回答