preface

This is the second day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021. 0.3-0.1 =? Println (0.3-0.1); println(0.3-0.1); println(0.3-0.1); println(0.3-0.1); The printed result is exactly the same, i.e. 0.19.. 8. For this kind of loss accuracy problem, if it appears in the calculation of commercial amount, the consequences will be unimaginable. The floating-point variable double can handle 16-bit significant numbers. However, in practice, larger or smaller numbers need to be computed and processed. To avoid loss of precision, you need to get the exact expected value from the decimal/large number calculation. At this point, you need to create BigDecimal objects that perform precise operations on numbers that are more than 16 significant bits.

createBigDecimalobject

An object is created by putting values into the BigDecimal constructor, with the following list of input arguments:

We’ll talk about MathContext objects and scale next.

Using different input parameter types to construct, the loss of precision problem occurs

Note: It is usually recommended to construct with a String input parameter. Otherwise, the expected result will be inconsistent.

Here’s an example:

// The input parameter type is not selected properly
BigDecimal onedotone1 = new BigDecimal(1.1);
System.out.println(onedotone1); / / 1.100000000000000088817841970012523233890533447265625
BigDecimal onedotone2 = new BigDecimal("1.1");
System.out.println(onedotone2); / / 1.1

double doubleNum = 3.14 D;
BigDecimal no = new BigDecimal(doubleNum);
System.out.println(no.doubleValue()); / / 3.14
BigDecimal yes1 = new BigDecimal(Double.toString(doubleNum));
System.out.println(yes1.doubleValue()); / / 3.14
// or
BigDecimal yes2 = BigDecimal.valueOf(1.1 D);
System.out.println(yes2.doubleValue()); / / 3.14

System.out.println(Error: when a decimal number is added to a Double: + no.toString() + ", compared with expectations:" + (Double.toString(doubleNum).equals(no.toString()) ? "The same": "Different"));
/ / type Double decimal into arguments, error writing results derived: 3.140000000000000124344978758017532527446746826171875, compared with the expected: not the same
System.out.println(When a decimal number of type Double is added to the argument: + yes.toString() + ", and expected size judgment:" + (Double.toString(doubleNum).equals(yes.toString()) ? "The same": "Different"));
// Double = 3.14; // Double = 3.14
Copy the code

Initialization with the constructor New BigDecimal(Double Val) can be somewhat unpredictable; the decimal passed in cannot be accurately represented as a double, that is, the decimal cannot be accurately represented on the computer. Although seemed to help us to construct a and expected values consistent BigDecimal objects, but the toString () = > 1.100000000000000088817841970012523233890533447265625, DoubleValue () => is the expected value.

The result of initialization with the new BigDecimal(String val) constructor is completely predictable, and the decimal String passed in is exactly the expected value, so it is generally recommended to construct with String inputs by comparison, Or an input parameter of type long/double is constructed using Bigdecimal.valueof ().

Round mode [Rounding Mode ]

scale

A very important concept in BigDecimal is the scale scale. What does it mean?

  • Scale >= 0 represents the number of digits to the right of the decimal point

  • Scale < 0 means value * (10 ^ | scale |) = > expected value, the value of x 10 scale absolute power

For example:

[unscaled value, scale] The resulting string
[123, 0] “123”
[- 123, 0] “123”
[123-1) “1.23 e+3”
[123-3] “1.23 e+5”
[123, 1) “12.3”
[123, 5] “0.00123”
[123, 10] “1.23 e-8”
[- 123, 12] “-1.23E-10”

RoundingMode For unscaled Value RoundingMode for unscaled Value

RoundingMode.HALF_UProunded

// Initializes the object and sets the scale and specifies the rounding mode
double result1 = BigDecimal.valueOf(1.356).setScale(2, RoundingMode.HALF_UP).doubleValue();
System.out.println("Rounding off positive numbers:" + result1); / / 1.36

double result2 = BigDecimal.valueOf(-1.356).setScale(2, RoundingMode.HALF_UP).doubleValue();
// Take the absolute value of a negative number and then round it to a negative number
System.out.println("Rounding negative numbers:" + result2); // -1.36
Copy the code

RoundingMode.HALF_DOWNSix into five shekels

double result1 = BigDecimal.valueOf(1.357).setScale(2, RoundingMode.HALF_DOWN).doubleValue();
System.out.println("Rounding off a positive five:" + result1); / / 1.36

double result2 = BigDecimal.valueOf(-1.355).setScale(2, RoundingMode.HALF_DOWN).doubleValue();
// Take the absolute value of a negative number and then round it by 5 or 6
System.out.println("Rounding negative numbers into five:" + result2); // -1.35
Copy the code

RoundingMode.HALF_EVENEven five, odd five

double result1 = BigDecimal.valueOf(20.245).setScale(2, RoundingMode.HALF_EVEN).doubleValue();
System.out.println("The reserved digits are even, rounded by five:" + result1); / / 20.24

double result2 = BigDecimal.valueOf(20.2635).setScale(3, RoundingMode.HALF_EVEN).doubleValue();
// First check the parity of the reserved digits, the odd number is rounded
System.out.println("The number of digits reserved is odd, rounded:" + result2); / / 20.264
Copy the code

RoundingMode.UPRounding away from zero

double result1 = BigDecimal.valueOf(1.23456789).setScale(3, RoundingMode.UP).doubleValue();
System.out.println("Rounding (positive number) away from zero:" + result1); / / 1.235

double result2 = BigDecimal.valueOf(-1.23456789).setScale(3, RoundingMode.UP).doubleValue();
System.out.println("(negative) rounding away from zero:" + result2); // -1.235
Copy the code

RoundingMode.DOWNRound to zero

double result1 = BigDecimal.valueOf(20.249999).setScale(2, RoundingMode.DOWN).doubleValue();
System.out.println("Rounding (a positive number) toward zero, i.e., dropping all bits following the reserved number:" + result1); / / 20.24

double result2 = BigDecimal.valueOf(-20.249999999).setScale(2, RoundingMode.DOWN).doubleValue();
System.out.println("(negative numbers) to round towards zero, i.e., to drop all bits following the reserved number:" + result2); // -20.24
Copy the code

RoundingMode.CEILINGCeiling rounding

double result1 = BigDecimal.valueOf(1.356).setScale(2, RoundingMode.CEILING).doubleValue();
System.out.println("(positive number) Ceiling rounding:" + result1); / / 1.36

double result2 = BigDecimal.valueOf(-1.356).setScale(2, RoundingMode.CEILING).doubleValue();
// The ceiling, as the name implies, is higher and larger on the side, -1.35 > -1.356 instead of -1.36 < -1.356
System.out.println("(negative) ceiling rounding:" + result2); // -1.35
Copy the code

RoundingMode.FLOORThe floor of rounding

double result1 = BigDecimal.valueOf(1.356).setScale(2, RoundingMode.FLOOR).doubleValue();
System.out.println("Positive floor rounding:" + result1); / / 1.35

double result2 = BigDecimal.valueOf(-1.356).setScale(2, RoundingMode.FLOOR).doubleValue();
// The floor is as the name implies, the lower, smaller side, -1.36 < -1.356, not -1.35 > -1.356
System.out.println("Negative floor rounding:" + result2); // -1.36
Copy the code

Note: Rounding mode does not increase the size of the calculated value.

Conclusion:

  1. Pawnchess: Take the absolute value first and then round it off. Pawnchess uses a negative sign after Rounding to make it negative

  2. RoundingMode.HALF_DOWN: the absolute value of a negative number is first rounded and then rounded into a negative number

  3. RoundingMode.HALF_EVEN: The reserved digits are even, rounded by five or six; The reserved digits are odd and rounded

  4. Roundingmode. UP: round away from the origin (zero)

  5. Roundingmode. DOWN: round to the origin (zero), that is, remove all digits except reserved ones

  6. Roundingmode. CEILING: round to a higher value

  7. Roundingmode. FLOOR: rounds the FLOOR to a smaller value

Common static methods

Creating a BigDecimal object is nothing more than adding, subtracting, multiplying, and dividing values mathematically:

BigDecimal.add(augend)

/ / augend
BigDecimal addend = new BigDecimal("100");
/ / addend
BigDecimal augend = BigDecimal.valueOf(32.123312 D);

// and = addend + addend
BigDecimal sum = addend.add(augend);
System.out.println("Sum of sum:" + sum); / / 132.123312
Copy the code

In addition, you can specify the significant number of digits for the result by setting the reserved Precision and RoundingMode (DEFAULT_ROUNDINGMODE = roundingmode.half_up), passing in the MathContext object as parameters.

MathContext specifies the effective number of digits (precision) and the rounding strategy
MathContext mathContext = new MathContext(5, RoundingMode.HALF_UP);
// equivalent to new MathContext(5);
BigDecimal sum = addend.add(augend, mathContext);
System.out.println("Pass the MathContext argument to specify the number of significant digits and the rounding strategy for the calculated result:" + sum); / / / / 132.12
Copy the code

BigDecimal.subtract(subtrahend)

/ / the minuend
BigDecimal minuend = new BigDecimal("100");
/ / reduction
BigDecimal subtrahend = BigDecimal.valueOf(32.123312 D);

// difference = minuend - subtraction
BigDecimal subtractResult = minuend.subtract(subtrahend);
System.out.println("The difference between the subtraction:" + subtractResult); / / 67.876688
Copy the code

Bigdecimal.multiply(multiplicand)

/ / the multiplicand
BigDecimal multiplicand = new BigDecimal("100");
/ / the multiplier
BigDecimal multiplier = BigDecimal.valueOf(32.123312 D);

// Product = multiplier × multiplicator
BigDecimal multiplyResult = multiplier.multiply(multiplier);
System.out.println("The product of multiplication:" + multiplyResult); / / 3212.331200
Copy the code

BigDecimal.divide(divisor)

/ / dividend
BigDecimal dividend = new BigDecimal("4.5");
/ / divisor
BigDecimal divisor = BigDecimal.valueOf(1.3 D);

// Quotient = dividend ÷ divisor
BigDecimal quotient = dividend.divide(divisor);
System.out.println("The quotient of division:" + quotient); // throws exception
Copy the code

Note: an error occurs when two Numbers not divisible: 4.5/1.3 Java. Lang. ArithmeticException: Non – terminating a decimal expansion; no exact representable decimal result.

So how do you solve the problems that can’t be solved?

It is very simple, not truncated enough, just keep the integer or keep the specified significant number:

// Method 1: keep the decimal place to two and round the third place
dividend.divide(divisor, 2, RoundingMode.HALF_UP);

// Method 2: Set the reserved precision, the default is round mode
dividend.divide(divisor, new MathContext(3));

// Method three: quotient round
dividend.divideToIntegralValue(divisor)
Copy the code

BigDecimal.divideAndRemainder(divisor)

BigDecimal[] remainder = dividend.divideAndRemainder(divisor);

System.out.println("Reserved integer bits:" + remainder[0]); / / 3
// remainder[0] <=> dividend.divideToIntegralValue(divisor)
System.out.println("Take remainder:" + remainder[1]); / / 0.6
// remainder[1] <=> dividend.remainder(divisor)
Copy the code

The final result returned is a two-element array containing the result of divideToIntegralValue followed by the result of remainder.

If you need an integer quotient and the rest, using this method is faster than using the corresponding methods separately, because the division only needs to be performed once.

BigDecimal.movePointLeft(n) | BigDecimal.movePointRight(n)

Move the decimal point n places to the left/right to return the value.

// 100 * 10^-2 = 1.00
System.out.println("Move the decimal point two places to the left:" + new BigDecimal(100).movePointLeft(2));

// 100 * 10^3 = 100000
System.out.println("Move the decimal three places to the right:" + new BigDecimal(100).movePointRight(3));
Copy the code

BigDecimal.precision()

Gets the precision of the initial value in a BigDecimal object.

System.out.println(new BigDecimal("3.14158").precision()); / / 6
System.out.println(new BigDecimal("123456").precision()); / / 6
Copy the code

BigDecimal.pow(n)

Returns the NTH power of the initial value in a BigDecimal object.

System.out.println(new BigDecimal(100).pow(2)); / / 100 ^ 2 = 10000
Copy the code

BigDecimal.abs()

Returns a BigDecimal with an absolute value.

System.out.println(new BigDecimal("100").abs()); / / | | - 100 = 100
Copy the code

BigDecimal.signum()

Returns -1, 0, and 1, representing negative, zero, and positive numbers respectively.

System.out.println(new BigDecimal("100").signum()); // 1
System.out.println(new BigDecimal("0").signum()); // 0
System.out.println(new BigDecimal("-100").signum()); // -1
Copy the code

BigDecimal.negate()

Returns a BigDecimal with a value plus a negative sign.

System.out.println(new BigDecimal("100").negate()); / / - > 100-100
System.out.println(new BigDecimal("200").negate()); // -200 -> -(-200) = 200
Copy the code

BigDecimal.compareTo(val)

Comparison of two numbers of type BigDecimal returns 1 if the left is greater than the right; Less than return -1; Equal returns 0.

System.out.println(new BigDecimal("100").compareTo(new BigDecimal("200"))); // -1
System.out.println(new BigDecimal("100").compareTo(new BigDecimal("100"))); / / 0
System.out.println(new BigDecimal("100").compareTo(BigDecimal.ZERO)); / / 1
Copy the code

BigDecimal.round(mathContext)

Set the number of significant digits, which are selected according to the rounding mode.

MathContext mathContext = new MathContext(4, RoundingMode.DOWN);
System.out.println(new BigDecimal("3.1415926").round(mathContext)); / / 3.141
Copy the code

Usage scenarios

The number of invitees for each province is a percentage of the total number of participants, reserved to two decimal places and rounded to the final result.

// Number of people invited
BigDecimal inviteNum = new BigDecimal("15");

BigDecimal totalNum = new BigDecimal("74");

// Create a percentage formatted reference
NumberFormat percent = NumberFormat.getPercentInstance(Locale.CHINA);
// The percentage is reserved for the maximum digit
percent.setMaximumFractionDigits(2);

// Divide two BigDecimal numbers, preserving the last four digits after the decimal point and rounding them off
BigDecimal quotient = inviteNum.divide(totalNum, 4, RoundingMode.HALF_UP);

// The result is converted to a percentage format
String proportion = percent.format(quotient);
System.out.println(proportion); / / 20.27%
Copy the code

Round the price in the shopping cart (keep two decimal places), return the price list in currency format after wiping the zero, return the account balance after empting the shopping cart, the balance is accurate to the minute.


/ * * * *@paramList of sales' unmonetized prices *@paramLocale Currency format *@returnReturns a list of prices */ in the specified currency format
private List<String> formatSale(List<Double> sales, Locale locale) {
    NumberFormat currency = NumberFormat.getCurrencyInstance(locale);
    DecimalFormat decimalFormat = new DecimalFormat("#");
    if(sales ! =null && sales.size() > 0) {
        return sales.stream()
                .map(sale -> {
                    String format = decimalFormat.format(sale);
                    return currency.format(Double.valueOf(format));
                }).collect(Collectors.toList());
    }
    return null;
}
Copy the code

// Account balance
BigDecimal account = BigDecimal.valueOf(100.36 D);
// Currency format
Locale locale = Locale.CHINA;
// Shopping cart price
List<Double> priceList = Arrays.asList(13.14 D.48.88 D.99.99 D.65.78 D.88.78 D);

// Empty the shopping cart and return the account balance after placing the order
Optional.ofNullable(formatSale(priceList, locale))
        .ifPresent(sales -> {
            // Total price of shopping cart items
            BigDecimal totalPrice = sales.stream()
                    .map(sale -> {
                        try {
                            return BigDecimal.valueOf(new DecimalFormat("RMB # # # #").parse(sale).doubleValue());
                        } catch (ParseException e) {
                            System.err.println(e.getMessage());
                            return BigDecimal.ZERO;
                        }
                    }).reduce(BigDecimal.ZERO, BigDecimal::add);
            // Empty the shopping cart balance
            BigDecimal balance = account.subtract(totalPrice);
            System.out.println(balance.compareTo(BigDecimal.ZERO) >=0 ? "After emptying the cart, the balance is:" + formatSale(Arrays.asList(balance.doubleValue()), locale) : "Eat dirt next month!"); // Eat dirt next month!
        });
Copy the code

Note: The NumberFormat abstract base class cannot be used to parse currency format prices, which cannot be instantiated. Its subclass DecimalFormat can be used to convert a string to a double-precision floating-point price based on the currency format of the price.

At the end

Writing is not easy, welcome everyone to like, comment, your attention, like is my unremitting power, thank you to see here! Peace and Love.