Preface
Today, a junior developer in the group asked why the tester found that the price calculation module he wrote had calculation deviations. He checked for a long time but couldn't find the problem. Here, Brother Xiaopang wants to remind you that you must use BigDecimal for commercial calculations. Floating point is inaccurate. Because computers cannot use binary decimals to accurately describe decimal decimals in our programs. In Article 48 of "Effective Java" also recommends "using BigDecimal to do precise operations". Today we will summarize the relevant knowledge points.
BigDecimal
BigDecimal represents an immutable arbitrary signed decimal number with a symbol. It consists of two parts:
- intVal - Integer with uncorrected precision, type BigInteger
- Scale - a 32-bit integer representing the number of digits to the right of the decimal point
For example, the uncorrected value of BigDecimal 3.14 is 314 and the scale is 2. We use BigDecimal for high-precision arithmetic operations. We also use it for calculations that require control over scale and rounding behavior. If your calculation is commercial, be sure to use the calculation precise BigDecimal.
Construct BigDecimal instance
We can create a BigDecimal object from String, character array, int, long and BigInteger:
@Test public void theValueMatches() { BigDecimal bdFromString = new BigDecimal("0.12"); BigDecimal bdFromCharArray = new BigDecimal(new char[]{'3', '.', '1', '4', '1', '5'}); BigDecimal bdlFromInt = new BigDecimal(42); BigDecimal bdFromLong = new BigDecimal(123412345678901L); BigInteger bigInteger = (100, new Random()); BigDecimal bdFromBigInteger = new BigDecimal(bigInteger); assertEquals("0.12", ()); assertEquals("3.1415", ()); assertEquals("42", ()); assertEquals("123412345678901", ()); assertEquals((), ()); }
We can also create BigDecimal from double:
@Test public void whenBigDecimalCreatedFromDouble_thenValueMayNotMatch() { BigDecimal bdFromDouble = new BigDecimal(0.1d); assertNotEquals("0.1", ()); }
We found that in this case the results are different from the expected results (i.e., 0.1). This is because: this conversion result is an exact decimal representation of the binary floating point value of the double, and its worth result is not something we can predict. We should use the String constructor instead of the double constructor. In addition, we can convert double to BigDecimal using the valueOf static method or directly use its uncorrected number to add decimal digits:
@Test public void whenBigDecimalCreatedUsingValueOf_thenValueMatches() { BigDecimal bdFromDouble = (0.1d); BigDecimal bigFromLong=(1,1); assertEquals("0.1", ()); assertEquals("0.1", ()); }
This method converts the double to its String representation before converting to BigDecimal. Additionally, it can reuse object instances. Therefore, we should prioritize the valueOf method to construct the function.
Common APIs
Method name | Interpretation of relevant usage of corresponding methods |
---|---|
abs() | Absolute value, scale remains unchanged |
add(BigDecimal augend) | Add, scale is the larger value of augend and the original value scale |
subtract(BigDecimal augend) | minus, scale is the larger value of augend and the original value scale |
multiply(BigDecimal multiplicand) | Multiply, scale is the sum of augend and the original value scale |
divide(BigDecimal divisor) | Divider, original value/divisor, if it cannot be divided, an exception will be thrown, the scale is the same as the original value |
divide(BigDecimal divisor, int roundingMode) | Divide, specify the rounding method, the scale is the same as the original value |
divide(BigDecimal divisor, int scale, int roundingMode) | Discard, specify the rounding method and scale |
remainder(BigDecimal divisor) | Take the remainder, the scale is the same as the original value |
divideAndRemainder(BigDecimal divisor) | After division operation, return an array to store the dividing and remainder. For example, 23/3, return {7,2} |
divideToIntegralValue(BigDecimal divisor) | Discard only the integer part, but the scale is still consistent with the original value. |
max(BigDecimal val) | Larger value, return the original value and the larger value in val, which is consistent with the result scale |
min(BigDecimal val) | Smaller value, consistent with the result's scale |
movePointLeft(int n) | The decimal point is shifted left, scale is the original value scale+n |
movePointRight(int n) | The decimal point is shifted right, scale is the original value scale+n |
negate() | Negative, scale remains unchanged |
pow(int n) | Power, original value ^n, power of original value |
scaleByPowerOfTen(int n) | Equivalent to the right shift of the decimal point n bit, the original value *10^n |
BigDecimal Operation
Operations on BigDecimal are just like other Number classes (Integer, Long, Double, etc.), BigDecimal provides operations for arithmetic and comparison operations. It also provides scaling operations, rounding and format conversion operations. It does not overload the arithmetic operator (+ - /*) or the logical operator (> < | &). Instead, we use BigDecimal's corresponding methods - addition, subtraction, multiplication, division and comparison. And BigDecimal has methods to extract various attributes.
Extract properties
Precision, decimal places and symbols:
@Test public void whenGettingAttributes_thenExpectedResult() { BigDecimal bd = new BigDecimal("-12345.6789"); assertEquals(9, ()); assertEquals(4, ()); assertEquals(-1, ()); }
Compare sizes
We use the compareTo method to compare the values of two BigDecimals:
@Test public void whenComparingBigDecimals_thenExpectedResult() { BigDecimal bd1 = new BigDecimal("1.0"); BigDecimal bd2 = new BigDecimal("1.00"); BigDecimal bd3 = new BigDecimal("2.0"); assertTrue((bd3) < 0); assertTrue((bd1) > 0); assertTrue((bd2) == 0); assertTrue((bd3) <= 0); assertTrue((bd2) >= 0); assertTrue((bd3) != 0); }
The above method ignores the decimal places when comparing. If you want to compare both precision and decimal places, please use the equals method:
@Test public void whenEqualsCalled_thenSizeAndScaleMatched() { BigDecimal bd1 = new BigDecimal("1.0"); BigDecimal bd2 = new BigDecimal("1.00"); assertFalse((bd2)); }
Four operations
BigDecimal provides the following four calculation methods:
- add ——Addition
- subtract ——Subtract
- divide - division, it is possible that it cannot be divided completely, and it must be explicitly declared that the number of decimal places should be kept to avoid throwing ArithmeticException exceptions
- multiply — multiply
@Test public void whenPerformingArithmetic_thenExpectedResult() { BigDecimal bd1 = new BigDecimal("4.0"); BigDecimal bd2 = new BigDecimal("2.0"); BigDecimal sum = (bd2); BigDecimal difference = (bd2); BigDecimal quotient = (bd2); BigDecimal product = (bd2); assertTrue((new BigDecimal("6.0")) == 0); assertTrue((new BigDecimal("2.0")) == 0); assertTrue((new BigDecimal("2.0")) == 0); assertTrue((new BigDecimal("8.0")) == 0); }
rounding
Since it is mathematical operation, we have to talk about rounding. For example, in the amount calculation, it is easy for us to encounter the final settlement amount of RMB 22.355. Because there is no unit with lower scores in currency, we need to use precision and rounding mode rules to cut numbers. Java provides two classes to control rounding behaviors RoundingMode and MathContext. MathContext executes the IEEE 754R standard but does not understand its usage scenarios at present. The one we use more is enumeration RoundingMode. It provides eight modes:
: Take the decimal as the origin, and take the right side of the positive number, and take the left side of the negative number
: Take the decimal digit as the origin, that is, take the left side of the positive number and take the right side of the negative number
: Take the nearest positive number on the left
: Take the nearest integer on the right
RoundingMode.HALF_DOWN: Round six, negative number first takes the absolute value and then round six and then negative number
RoundingMode.HALF_UP: Rounding, negative numbers are the same as above
RoundingMode.HALF_EVEN: This is more circumferential. If the integer digit is odd, it will be rounded. If it is even, it will be rounded.
RoundingMode.ROUND_UNNECESSARY: No need to round. If there are decimal places, throw an ArithmeticException exception.
format
Digital formatting can be operated through the operation class and the provided API. Actually we only need to use it because it proxys NumberFormat. Let's take a look at their APIs:
NumberFormat
(Locale), getNumberInstance(Locale). Returns the general numeric format for the specified locale.
(Locale). Returns the currency format for the specified locale.
(Locale). Returns the percentage format of the specified locale.
(Locale). Returns the integer numeric format of the specified locale.
(int). Sets the minimum number of digits allowed by the integer part of the number.
(int). Sets the maximum number of digits allowed by the integer part of the number.
(int). Set the minimum number of decimal points, and the insufficient number of bits is filled with 0. If it exceeds it, it is output according to the actual number of bits.
(int). Set the maximum number of decimal places to keep, and do not add 0 if it is not enough.
DecimalFormat
In addition to being able to proxy NumberFormat above, DecimalFormat also provides a formatting style based on pattern strings, which is a bit similar to formatting time. Let's take a look at the rules of pattern:
- "0" - represents a one-digit value. If not, 0 will be displayed. For example, "0000.0000", integer or decimal place > 4, according to the actual output, <4 integer place 0 before the decimal place and then add 0 after the decimal place, and make up 4 digits.
- "#" - an integer representing any number of digits. If not, it will not be displayed. Used in decimal places, it only represents one decimal, and the excess part is rounded. For example: "#": There is no decimal, and the decimal part is rounded. ".#": The integer part remains unchanged, one decimal, rounded. ".##": The integer part remains unchanged, two decimal places, rounded.
- "." - means the decimal point. Note that an exception will only appear once in a pattern, and more than once will be formatted.
- "," - used with pattern "0" to indicate a comma. Note that you must not use it after the decimal point, otherwise the formatting will be abnormal.
Summarize
Today I have summarized BigDecimal. I suggest you bookmark this article for later use, and you can also transfer it to other students in need.
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.