Bootstrap

bigdecimal类_BigDecimal类的常见陷阱以及如何避免它们

bigdecimal类

When doing business calculations in Java, especially when it comes to currencies, you would preferably use the java.math.BigDecimal class to avoid the problems related to floating-point arithmetic, which you might experience if using one of the two primitive types float or double (or one of their boxed type counterparts).Indeed, the BigDecimal class contains a number of methods that can meet most of the requirements of common business calculations.However, I would like to draw your attention to the most common pitfalls in the use of the BigDecimal class and show you how to avoid them, by using the regular BigDecimal API on the one hand and a new, customized class that extends BigDecimal on the other.So, let’s start with the regular BigDecimal API.

在Java中进行业务计算时,特别是在涉及货币时,最好使用java.math.BigDecimal类来避免与浮点算术有关的问题,如果使用两种基本类型float或浮点型之一可能会遇到这些问题。 double(或与之对应的盒装类型之一)。实际上, BigDecimal类包含许多方法,这些方法可以满足大多数常规业务计算的要求。但是,我想提醒您注意使用中最常见的陷阱BigDecimal类,并告诉您如何避免它们,使用,一方面和延伸的BigDecimal在other.So一个新的,定制类的常规BigDecimal的 API,让我们与常规的BigDecimal API开始。

陷阱1:双重构造函数 (Pitfall #1: The double constructor)

Consider the following example:

考虑以下示例:

BigDecimal x = new BigDecimal(0.1);
System.out.println("x=" + x);Console output: x=0.1000000000000000055511151231257827021181583404541015625

As you can see, the result of this constructor can be somewhat unpredictable. This is because floating-point numbers are represented in computer hardware as base 2 (binary) fractions. However, most decimal fractions cannot be represented exactly as binary fractions. Therefore, the binary floating-point numbers actually stored in the machine only approximate the decimal floating-point numbers you enter. Hence, the value passed to the double constructor is not exactly equal to 0.1.

如您所见,此构造函数的结果可能有些不可预测。 这是因为浮点数在计算机硬件中表示为基数2(二进制)的分数。 但是,大多数十进制分数不能完全表示为二进制分数。 因此,实际存储在机器中的二进制浮点数仅近似于您输入的十进制浮点数。 因此,传递给double构造函数的值不完全等于0.1。

In contrast, the String constructor is perfectly predictable and produces a BigDecimal which is exactly equal to 0.1 as expected.

相反, String构造函数是完全可预测的,并且会产生一个BigDecimal ,该BigDecimal与预期的完全相等,等于0.1。

BigDecimal y = new BigDecimal("0.1");
System.out.println("y=" + y);Console output:
y=0.1

Thus, it is recommended to use the String constructor in preference to the double constructor.If, for any reason, a double must be used to create a BigDecimal, consider using the static BigDecimal.valueOf(double) method.This will give the same result as converting the double to a String using the Double.toString(double) method and then using the BigDecimal(String) constructor.

因此,建议使用String构造函数优先于double构造函数。如果出于任何原因必须使用double来创建BigDecimal ,请考虑使用静态BigDecimal.valueOf(double)方法。导致如使用Double.toString(double)方法转换到双String,然后使用BigDecimal(String)构造。

陷阱2:静态valueOf(double)方法 (Pitfall #2: The static valueOf(double) method)

If using the static BigDecimal.valueOf(double) method to create a BigDecimal, be aware of its limited precision:

如果使用静态BigDecimal.valueOf(double)方法创建BigDecimal ,请注意其有限的精度:

BigDecimal x = BigDecimal.valueOf(1.01234567890123456789); BigDecimal y = new BigDecimal("1.01234567890123456789"); System.out.println("x=" + x);
System.out.println("y=" + y);Console output:
x=1.0123456789012346
y=1.01234567890123456789

Here, the x value has lost 4 decimal digits because a double has only a precision of 15–17 digits (a float only of 6–9 digits), while a BigDecimal is of arbitrary precision (limited only by memory).Therefore, it is actually a good idea to use the String constructor, since two major problems caused by the double constructor are effectively avoided.

在这里,x值丢失了4个十进制数字,因为双精度仅具有15-17个数字的精度(浮点数仅6-9个数字),而BigDecimal具有任意精度(仅受内存限制)。使用String构造函数实际上是一个好主意,因为可以有效避免由double构造函数引起的两个主要问题。

陷阱3:equals(bigDecimal)方法 (Pitfall #3: The equals(bigDecimal) method)

Let’s take a look at this example:

让我们看一下这个例子:

BigDecimal x = new BigDecimal("1");
BigDecimal y = new BigDecimal("1.0"); System.out.println(x.equals(y));Console output:
false

This is because a BigDecimal consists of an unscaled integer value with arbitrary precision and a 32-bit integer scale, both of which must be equal to the corresponding values of the BigDecimal to be compared:

这是因为BigDecimal由具有任意精度和32位整数小数位数的未缩放整数值组成,这两者必须等于要比较的BigDecimal的对应值:

  • x has an unscaled value of 1 and a scale of 0

    x的无标度值为1,小数位数为0
  • y has an unscaled value of 10 and a scale of 1

    y的无标度值为10,小数位数为1

Hence, x is not equal to y.For this reason, BigDecimals shouldn’t be compared with the equals() method, but with the compareTo() method, which instead compares the numerical values (x=1; y=1.0) represented by the two BigDecimals.

因此,x不等于y。因此,不应将BigDecimalsequals()方法进行比较,而应与compareTo()方法进行比较,该方法将比较表示的数值(x = 1; y = 1.0)由两个BigDecimals组成

System.out.println(x.compareTo(y) == 0);Console output:
true

陷阱4:round(mathContext)方法 (Pitfall #4: The round(mathContext) method)

If you’re unfamiliar with the BigDecimal class, you might be tempted to use the round(new MathContext(precision, roundingMode)) method, because you want to round your BigDecimal to let’s say two decimal places.

如果您不熟悉BigDecimal类,则可能会想使用round(new MathContext(precision,roundingMode))方法,因为您想将BigDecimal 舍入为两个小数位。

BigDecimal x = new BigDecimal("12345.6789");
x = x.round(new MathContext(2, RoundingMode.HALF_UP)); System.out.println("x=" + x.toPlainString()); System.out.println("scale=" + x.scale());Console output:
x=12000 (and not as expected 12345.68)
scale=-3 (and not as expected 2)

Well, this doesn’t round, as you would expect, the fractional part but it does round the unscaled value to the given number of significant digits (counting from the left to the right), leaving the decimal point untouched, which will result in the example above in a negative scale of -3 !

好吧,这不会像您期望的那样四舍五入,但是会将未缩放的值四舍五入到给定数量的有效数字(从左到右计数),保持小数点不变,这将导致上面的示例的负比例为-3!

So, what has happened here?The unscaled value (123456789) has been rounded to 2 significant digits (12), which represents a precision of 2. However, as the decimal point has been left untouched the real value represented by this BigDecimal is 12000.0000. This can also be written as 12000 since the 4 zeroes to the right of the decimal point are meaningless.

因此,这里发生了什么?未缩放的值(123456789)已四舍五入为2个有效数字(12),表示精度为2。但是,由于保留了小数点,此BigDecimal表示的实际值为12000.0000 。 这也可以写为12000,因为小数点右边的4个零是没有意义的。

But what about the scale? Why is it -3 and not 0, as you would expect for a value of 12000?That is because the unscaled value of this BigDecimal is 12 and thus has to be multiplied by 1000 which is ten to the power of 3 (12 * 10³) to become 12000.

但是规模呢? 为什么它是-3而不是0(如您期望的值12000)?这是因为此BigDecimal的未缩放值是12,因此必须乘以1000,即10乘以3的幂(12 *10³ )成为12000。

Hence, a positive scale represents the number of fraction digits (number of digits to the right of the decimal point), whereas a negative scale the number of insignificant digits to the left of the decimal point (in this case the trailing zeros since they are only placeholders to indicate the scale of the number).Finally, the number represented by a BigDecimal is therefore:

因此,正标度代表小数位数(小数点右边的位数),而负标度代表小数点左边的无关紧要数字(在这种情况下,尾随零是因为仅占位符表示数字的小数位数。最后, BigDecimal表示的数字为:

unscaledValue * 10-scale (unscaledValue multiplied by ten to the power of -scale)

unscaledValue * 10-scale(unscaledValue乘以十乘以-scale的幂)

Also note that in the above example we use the toPlainString() method, which avoids displaying the result in scientific notation (1.2E+4).

还要注意,在上面的示例中,我们使用了toPlainString()方法,该方法避免以科学计数法(1.2E + 4)显示结果。

To get the expected result of 12345.68, the setScale(scale, roundingMode) method has to be used:

要获得12345.68的预期结果,必须使用setScale(scale,roundingMode)方法:

BigDecimal x = new BigDecimal("12345.6789");
x = x.setScale(2, RoundingMode.HALF_UP);
System.out.println("x=" + x));Console output:
x=12345.68

This method rounds the fraction part to 2 decimal places according to the specified rounding mode.

此方法根据指定的舍入模式将小数部分舍入到小数点后两位。

Nevertheless, it is possible to use the round(new MathContext(precision, roundingMode)) method for conventional rounding. But that would require you to know the total number of digits to the left of the decimal point of the calculation result.

但是,可以使用round(new MathContext(precision,roundingMode))方法进行常规舍入。 但这将需要您知道计算结果小数点左边的位数。

BigDecimal a = new BigDecimal("12345.12345");
BigDecimal b = new BigDecimal("23456.23456");
BigDecimal c = a.multiply(b);
System.out.println("c=" + c);Console output:
c=289570111.3153564320

To round c to 2 decimal places, you would have to use a MathContext object with a precision of 11.

要将c舍入到小数点后两位,您必须使用MathContext对象,其精度为11。

BigDecimal d = c.round(new MathContext(11, RoundingMode.HALF_UP)); System.out.println("d=" + d);Console output:
d=289570111.32

The total number of digits to the left of the decimal point can be calculated like this:

小数点左边的位数总数可以这样计算:

bigDecimal.precision() - bigDecimal.scale() + newScale

bigDecimal.precision()-bigDecimal.scale()+ newScale

where:

哪里:

  • bigDecimal.precision() is the precision of the unrounded result

    bigDecimal.precision()是未取整结果的精度

  • bigDecimal.scale() is the scale of the unrounded result

    bigDecimal.scale()是未取整结果的小数位数

  • newScale is the scale you want to round to

    newScale是您要舍入的比例

BigDecimal e = c.round(new MathContext(c.precision() — c.scale() + 2, RoundingMode.HALF_UP));
System.out.println("e=" + e);Console output:
e=289570111.32

However, if you compare this expression:

但是,如果比较此表达式:

c.round(new MathContext(c.precision() — c.scale() + 2, RoundingMode.HALF_UP));

to that one:

到那个:

c.setScale(2, RoundingMode.HALF_UP);

it’s obvious which one you would choose to write readable and concise code.

很明显,您会选择编写清晰可读的代码。

So far, we have seen the most common pitfalls of the BigDecimal class and how they can be avoided.But wouldn’t it be better to have a class that could handle most of these issues so that you don’t risk falling into one of those traps?Well, this is possible by extending the BigDecimal class.In the following, I’m going to show you one possible way how this can be achieved.

到目前为止,我们已经看到了BigDecimal类中最常见的陷阱以及如何避免这些陷阱,但是拥有可以处理大多数这些问题的类不是更好,这样您就不必冒陷入其中之一的风险了。那么,可以通过扩展BigDecimal类来实现。在下面,我将向您展示一种实现此目标的可能方法。

First, we need a name for our new class, which I’m going to call in this example simply Decimal.So Decimal is going to extend BigDecimal and thus inherits all public fields and methods from its superclass.However, to call our own methods on an instance of the new class, we need to override every method of the BigDecimal class that returns a BigDecimal instance in order to return a Decimal instance instead.As these are quite a few, we’re going to generate delegate methods with the help of our favorite IDE and then change the code so that it will return the correct type.

首先,我们需要为新类命名,在本例中将简称Decimal ,因此Decimal将扩展BigDecimal并因此继承其超类的所有公共字段和方法,但是要调用我们自己的方法在新类的实例上,我们需要重写返回BigDecimal实例的BigDecimal类的每个方法,以便返回Decimal实例。由于这些实例很多,我们将在帮助下生成委托方法我们最喜欢的IDE,然后更改代码,使其返回正确的类型。

So, for instance this:

因此,例如:

@Override
public BigDecimal add(BigDecimal augend) {
return super.add(augend);
}

will become that:

将变为:

@Override
public Decimal add(BigDecimal augend) {
return new Decimal(super.add(augend));
}

Note that besides the new BigDecimal instance created by the super.add() method, we are also creating a new instance of the Decimal class here.But since BigDecimal is immutable, I would prefer the new class to be immutable too.

请注意,除了通过super.add()方法创建的新BigDecimal实例之外,我们还在此处创建了Decimal类的新实例。但是由于BigDecimal是不可变的,因此我也希望新类也是不可变的。

One could also imagine a mutable decimal class, which would avoid the creation of a second object. In this case, you have to be aware that it changes the behavior of the new class, which in turn can lead to other pitfalls.

人们还可以想象一个可变的十进制类,它将避免创建第二个对象。 在这种情况下,您必须意识到它会更改新类的行为,从而可能导致其他陷阱。

So, the method a.add(b) will now return a Decimal instead of a BigDecimal instance.

因此,方法a.add(b)现在将返回Decimal而不是BigDecimal实例。

Decimal a = new Decimal("12345.12345");
Decimal b = new Decimal("23456.23456");
Decimal c = a.add(b);

On object c, we are now able to call our own methods that do not exists yet.

现在,在对象c上,我们可以调用自己尚不存在的方法。

Before creating some new methods, I want to add some constructors to the newly created Decimal class.Since constructors cannot be inherited in Java, because the constructor of the subclass has to have a different name than the constructor of the superclass (the name of the constructor must match the name of the class), we have to implement the constructors with the same arguments as those in the superclass ourselves.In the following, I’m going to add only those constructors that make sense from a business logic developer’s perspective and that will avoid most of the issues discussed above:

在创建新方法之前,我想向新创建的Decimal类中添加一些构造函数。由于无法在Java中继承构造函数,因为子类的构造函数必须具有与超类的构造函数不同的名称(构造函数必须与类的名称匹配),我们必须使用与超类中相同的参数来实现构造函数。在下文中,我将仅添加从业务逻辑开发人员的角度来看有意义的那些构造函数,以及这样可以避免上面讨论的大多数问题:

Constructor to create a Decimal instance from an int:

构造函数,从int创建Decimal实例:

public Decimal(int val) {
super(val);
}

A constructor with the same argument exits in the superclass, which is also called in the body of the above constructor.

具有相同参数的构造函数在超类中退出,在上述构造函数的主体中也将调用它。

Constructor to create a Decimal instance from a long:

构造函数从一个long中创建一个Decimal实例:

public Decimal(long val) {
super(val);
}

A constructor with the same argument exits in the superclass, which is also called in the body of the above constructor.

具有相同参数的构造函数在超类中退出,在上述构造函数的主体中也将调用它。

Constructor to create a Decimal instance from a double:

构造函数从双精度创建Decimal实例:

public Decimal(double val) {
super(Double.toString(val));
}

A constructor with the same argument exits in the superclass, but is not called in the body of the above constructor. Instead, the String constructor is called after the double has been converted to a String to avoid the issues seen in pitfall #1. I will come back to this constructor later on to discuss possible further issues.

具有相同参数的构造函数在超类中退出,但在上述构造函数的主体中未调用。 相反,在将double转换为String之后,将调用String构造函数,以避免在陷阱1中看到的问题。 稍后,我将返回此构造函数讨论可能的其他问题。

Constructor to create a Decimal instance from a BigDecimal:

从BigDecimal创建Decimal实例的构造方法:

public Decimal(BigDecimal val) {
super(val.unscaledValue(), val.scale());
}

There is no equivalent constructor in the superclass, as this would not make any sense, except maybe for cloning a BigDecimal. To do so, you can use the same constructor as the one called above, which creates a new BigDecimal from an unscaled value and a scale.

在超类中没有等效的构造函数,因为除了克隆BigDecimal之外,这没有任何意义。 为此,您可以使用与上面调用的构造器相同的构造器,该构造器根据未缩放的值和小数位数创建一个新的BigDecimal

Constructor to create a Decimal instance from a string representation of a number:

构造函数,使用数字的字符串表示形式创建Decimal实例:

public Decimal(String val) {
super(val);
}

A constructor with the same argument exits in the superclass, which is also called in the body of the above constructor.

具有相同参数的构造函数在超类中退出,在上述构造函数的主体中也将调用它。

Constructor to create a Decimal instance from a formatted string representation of a number:

构造函数,使用数字的格式化字符串表示形式创建Decimal实例:

public Decimal(String val, FormatInfo info) throws ParseException {
super(getFormatInstance(info).parse(val).toString());
}

There is no equivalent constructor in the superclass, since the BigDecimal class does not handle any formatting issues.

在超类中没有等效的构造函数,因为BigDecimal类不处理任何格式问题。

I’m aware that the new double constructor may lead to issues due to a double’s limited precision. Nevertheless, I must admit that I personally prefer the double to the String constructor, because it feels more natural to write numbers as what they are, namely numbers and not strings. It is also less error prone because the numbers you enter are recognized as such by the compiler.

我知道,由于double的精度有限,因此新的double构造函数可能会导致问题。 不过,我必须承认,我个人更喜欢double而不是String构造函数,因为将数字写成数字本身(即数字而不是字符串)感觉更自然。 这也不太容易出错,因为编译器会识别出您输入的数字。

I give you an example for this: Most countries in Europe use the comma instead of the point as a decimal separator, which could lead to the following error at runtime:

我为此提供一个示例:欧洲大多数国家/地区使用逗号而不是点作为小数点分隔符,这可能会在运行时导致以下错误:

new Decimal("1000,45") -> java.lang.NumberFormatException: Character , is neither a decimal digit number, decimal point, nor "e" notation exponential mark.

Another example might be using non numeric characters due to typos with the String constructor:

另一个示例可能由于String构造函数的错别字而使用非数字字符:

new Decimal("100o.45") -> java.lang.NumberFormatException: Character o is neither a decimal digit number, decimal point, nor "e" notation exponential mark.

These issues can be avoided when using the double constructor, because your IDE is precompiling the code while saving and thus immediately gives feedback on its syntactic correctness.

使用double构造函数时可以避免这些问题,因为您的IDE在保存时预编译了代码,因此会立即就其语法正确性提供反馈。

While this piece of code new BigDecimal(“1000,45”), which is syntactically correct, will lead to an exception only at runtime, this other piece of code new BigDecimal(1000,45), which is syntactically wrong, produces an error already at compile time and thus can be corrected immediately.

在语法上正确的这段代码new BigDecimal(“ 1000,45”)仅会在运行时导致异常,而在语法上错误的另一段代码new BigDecimal(1000,45)则会产生错误已经在编译时,因此可以立即更正。

Another argument in favor of the double constructor is that you can use the thousands separator in order to increase the readability of the numbers in your code, which is not possible with the String constructor:

支持double构造函数的另一个参数是,可以使用千位分隔符来增加代码中数字的可读性,而String构造函数则无法实现:

new Decimal(1_000_000.45) // -> works fine
new Decimal("1_000_000.45") // -> java.lang.NumberFormatException: Character _ is neither a decimal digit number, decimal point, nor "e" notation exponential mark.

And this won’t work either because the regular thousands separator is not recognized as such by the String constructor:

而且这也不起作用,因为String构造函数无法识别常规的千位分隔符:

new Decimal("1,000,000.45") // -> java.lang.NumberFormatException: Character , is neither a decimal digit number, decimal point, nor "e" notation exponential mark.

To handle a possible loss of precision, you can check the resulting precision of the newly created Decimal and throw an exception if it is greater than 14 (which does not mean that it has already lost precision, but could possibly have).

要处理可能的精度损失,可以检查新创建的Decimal的最终精度,如果大于14,则抛出异常(这并不意味着它已经失去精度,但有可能已经存在)。

public Decimal(double val) {
super(Double.toString(val));
if (precision() > 14) {
throw new IllegalArgumentException("Possible loss of precision. Use the String constructor instead!");
}
}

Note that the above constructor should help to avoid pitfall #1, but also pitfall #2 in the sense that an IllegalArgumentExecption is thrown in case of a possible loss of precision, which then would at least not happen unnoticed.

请注意,上述构造函数应有助于避免陷阱#1,但也可以避免陷阱#2,因为在可能导致精度损失的情况下抛出了IllegalArgumentExecption ,这种情况至少不会被忽视。

However, if you still think that the use of the double constructor is too risky, just omit it and use the String constructor instead. That way, you are sure to avoid both pitfalls #1 and #2.

但是,如果您仍然认为使用double构造函数过于冒险,则可以忽略它,而使用String构造函数。 这样,您一定要避免陷阱#1和#2。

Now, that we have added our constructors, we’re going to add some new methods to make it easier to compare 2 decimal numbers.

现在,我们已经添加了构造函数,我们将添加一些新方法以使比较2个十进制数字变得更加容易。

The recommended way to compare 2 BigDecimals is to use the compareTo() method:

建议比较2个BigDecimals的方法是使用compareTo()方法:

Decimal a = new Decimal(12345.12345);
Decimal b = new Decimal(23456.23456);
a.compareTo(b) == 0 // false
a.compareTo(b) >= 0 // false
a.compareTo(b) <= 0 // true
a.compareTo(b) > 0 // false
a.compareTo(b) < 0 // true

But frankly, this way of comparing numbers is neither obvious nor well readable, and could potentially lead to misinterpretation.

但坦率地说,这种比较数字的方法既不明显也不易读,并且可能导致误解。

That’s why I’d like to introduce 5 new methods to compare decimal numbers in a clearer and more concise way:

因此,我想介绍5种新方法,以一种更清晰,更简洁的方式比较十进制数字:

a.equalTo(b)
a.greaterOrEqualTo(b)
a.lessOrEqualTo(b)
a.greaterThan(b)
a.lessThan(b)

I believe that anyone reading this code will immediately understand the new methods correctly.

我相信任何阅读此代码的人都会立即正确理解新方法。

Note that the equalTo() method should help to avoid pitfall #3.

请注意, equalTo()方法应有助于避免陷阱#3。

In the implementation of the 5 new methods we would of course use the compareTo() method:

在实现这5种新方法时,我们当然会使用compareTo()方法:

public boolean equalTo(Decimal decimal) {
return this.compareTo(decimal) == 0;
}
public boolean greaterOrEqualTo(Decimal decimal) {
return this.compareTo(decimal) >= 0;
}
public boolean lessOrEqualTo(Decimal decimal) {
return this.compareTo(decimal) <= 0;
}
public boolean greaterThan(Decimal decimal) {
return this.compareTo(decimal) > 0;
}
public boolean lessThan(Decimal decimal) {
return this.compareTo(decimal) < 0;
}

Now, let’s address the rounding issues of the BigDecimal class by adding the following new method:

现在,让我们通过添加以下新方法来解决BigDecimal类的舍入问题:

public Decimal rounding(RoundingInfo info) {
return setScale(info.scale(), info.mode());
}

This method uses an interface, which is implemented by an enum to specify a scale and a rounding mode. The interface lets you create your own Rounding enum with rounding types according to your needs.

此方法使用由枚举实现的接口来指定比例和舍入模式。 该界面允许您根据需要使用舍入类型创建自己的舍入枚举。

public interface RoundingInfo {    int scale();
RoundingMode mode();}public enum Rounding implements RoundingInfo { AMOUNT(2, RoundingMode.HALF_UP),
RATE(6, RoundingMode.HALF_UP),
SURFACE(4, RoundingMode.HALF_UP); private final int scale;
private final RoundingMode mode; private Rounding(int scale, RoundingMode mode) {
this.scale = scale;
this.mode = mode;
} @Override
public int scale() {
return scale;
} @Override
public RoundingMode mode() {
return mode;
}}

This allows us to specify preset rounding settings, the ones we use in our daily work. That way, you do not have to specify neither the scale nor the rounding mode yourself. Especially developers not familiar with the different rounding modes, or ignoring the one currently in use, could easily choose the wrong one. You could even completely ignore the scale and rounding modes as long as you know what type of number you are dealing with (currency, rate, etc.).

这使我们可以指定预设舍入设置,这是我们在日常工作中使用的设置。 这样,您不必自己指定缩放比例或舍入模式。 特别是不熟悉不同舍入模式或忽略当前使用的舍入模式的开发人员,很容易选择错误的舍入模式。 只要知道要处理的数字类型(货币,汇率等),您甚至可以完全忽略比例和舍入模式。

Decimal a = new Decimal(12345.12345);
Decimal b = new Decimal(23456.23456);
Decimal c = a.multiply(b).round(Rounding.AMOUNT)
System.out.println("c=" + c);Console output:
c=289570111.32

Note that the new rounding method should help to avoid pitfall #4.

请注意,新的舍入方法应有助于避免陷阱4。

Finally, let’s add a method to format our Decimal. Why should a class not be able to render itself in an appropriate way?

最后,让我们添加一个方法来格式化Decimal 。 为什么类不能以适当的方式呈现自己?

Method to format a decimal according to the specified format info:

根据指定的格式信息格式化小数的方法:

public String format(FormatInfo info) {
return getFormatInstance(info).format(this);
}private static DecimalFormat getFormatInstance(FormatInfo info) {
DecimalFormat format = (DecimalFormat)DecimalFormat.getInstance(info.locale());
format.applyPattern(info.pattern());
return format;
}

In the same way as for rounding, this method uses an interface that is implemented by an enum to specify a pattern and a locale:

与舍入相同,此方法使用由枚举实现的接口来指定模式和语言环境:

public interface FormatInfo {
String pattern();
Locale locale();
}public enum Format implements FormatInfo { AMOUNT("#,##0.00", Locale. US),
RATE("#,##0.000000", Locale. US),
SURFACE("#,##0.0000", Locale. US); private final String pattern;
private final Locale locale; private Format(String pattern, Locale locale) {
this.pattern = pattern;
this.locale = locale;
} @Override
public String pattern() {
return pattern;
} @Override
public Locale locale() {
return locale;
}}

This allows us again to specify preset formats, matching the previously defined rounding settings. In this case, we also do not have to deal with the correct pattern and locale used for the formatting, which in turn leads to less error prone code.

这使我们可以再次指定预设格式,以匹配先前定义的舍入设置。 在这种情况下,我们也不必处理用于格式化的正确模式和语言环境,从而减少了容易出错的代码。

Decimal a = new Decimal(12345.12345);
Decimal b = new Decimal(23456.23456);
Decimal c = a.multiply(b).round(Rounding.AMOUNT);
System.out.println("c=" + c.format(Format.AMOUNT));Console output:
c=289,570,111.32

Now that we are able to address the 4 pitfalls discussed above, but also to do some basic formatting operations on our new class, let’s see how this works all together by doing a real-world example.

既然我们已经能够解决上面讨论的4个陷阱,而且还可以对新类进行一些基本的格式化操作,那么让我们通过一个真实的示例来了解它们如何一起工作。

So, for instance, let’s calculate the compound interest:

因此,例如,让我们计算复利:

Decimal principal = new Decimal(12_345.67);
Decimal rate = new Decimal(0.0456);
int compounds = 12;
int years = 7;Decimal amount = principal.multiply(
Decimal.ONE.add(
rate.divide(new Decimal(compounds))
).pow(compounds * years)
).rounding(Rounding.AMOUNT);assertTrue(amount.equalTo(new Decimal(16_977.7)));System.out.println("amount=" + amount.format(Format.AMOUNT));Console output:
amount=16,977.70

This is the same example using the regular BigDecimal API:

这是使用常规BigDecimal API的相同示例:

BigDecimal principal = new BigDecimal("12345.67");
BigDecimal rate = new BigDecimal("0.0456");
int compounds = 12;
int years = 7;BigDecimal amount = principal.multiply(
BigDecimal.ONE.add(
rate.divide(new BigDecimal(compounds))
).pow(compounds * years)
).setScale(2, RoundingMode.HALF_UP);assertTrue(amount.compareTo(new BigDecimal("16977.7")) == 0);DecimalFormat formatter = (DecimalFormat)DecimalFormat.getInstance(Locale.US);
formatter.applyPattern("#,##0.00");
System.out.println("amount=" + formatter.format(amount));Console output:
amount=16,977.70

Note that any relevant differences are marked in bold. I let you decide which of these 2 versions is better readable, more concise and less error prone.

请注意,所有相关的差异均以粗体标出。 我让您决定这两个版本中的哪个版本更易读,更简洁并且更不易出错。

结论 (Conclusion)

We have seen the most common pitfalls of the BigDecimal class and learned how to avoid them using either the regular BigDecimal API or a custom decimal class that extends BigDecimal.

我们已经看到了BigDecimal类的最常见陷阱,并学习了如何使用常规BigDecimal API或扩展BigDecimal的自定义十进制类来避免它们。

When used correctly, the BigDecimal class is well suited for any kind of calculations where decimal values need to remain exact, especially when dealing with currencies, and thus meets most of the core requirements of a business logic developer.

如果正确使用, BigDecimal类非常适合需要精确保留十进制值的任何类型的计算,尤其是在处理货币时,因此可以满足业务逻辑开发人员的大多数核心要求。

翻译自: https://medium.com/javarevisited/common-pitfalls-of-the-bigdecimal-class-and-how-to-avoid-them-86d0e8c978fa

bigdecimal类

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;