Numerical calculation is inevitable in our daily work, especially in the e-commerce system. In general, we pay special attention to this problem, but if we don’t pay attention to it, there will be a big problem. There is no trivial matter related to money. Now the new brother accidentally overturned his car in this small sewer, making a joke.

In order that we can avoid making mistakes on this issue in the future, I specially wrote this article today to summarize.

Avoid using doubles

USES Double to calculate, we think that the arithmetic and the computer is not always, this is because the computer is a value stored in a binary, we enter a decimal value will be converted into a binary calculation, turn the decimal binary and then converted to a decimal, it is not the original decimal, is no longer had the boy. For example: decimal 0.1 converted to binary is 0.0 0011 0011 0011… (0011), and then converted to a decimal is 0.1000000000000000055511151231, see, not lying to you.

It is inevitable that computers cannot express floating-point numbers accurately, which is why floating-point numbers lose accuracy after calculation.

System.out.println(0.1+0.2);
System.out.println(1.0-0.8);
System.out.println(4.015*100);
System.out.println(123.3/100);
Copy the code

Through a simple example, we found that the accuracy of the loss is not big, but it doesn’t mean we can use, especially the electricity class system, the less each day said that millions of orders, each order even calculate a penny less, it is a not small amount, so, this is not a little things, and then a lot of people said, amount of calculation, You use BigDecimal, right, there’s nothing wrong with that, but you use BigDecimal and you’re done? When asked this sentence, there must be a catch.

BigDecimal Which pits have you encountered?

Using a simple example, let’s look at the result:

System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2)));
System.out.println(new BigDecimal(1.0).subtract(new BigDecimal(0.8)));
System.out.println(new BigDecimal(4.015).multiply(new BigDecimal(100)));
System.out.println(new BigDecimal(123.3).divide(new BigDecimal(100)));
Copy the code

We found that the results were still inaccurate after using BigDecimal, so remember the first pit of BigDecimal:

When BigDecimal is used to represent and evaluate floating point numbers, the String constructor is used to initialize BigDecimal.

Let’s look at the results with a few minor tweaks:

System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));
System.out.println(new BigDecimal("1.0").subtract(new BigDecimal("0.8")));
System.out.println(new BigDecimal("4.015").multiply(new BigDecimal("100")));
System.out.println(new BigDecimal("123.3").divide(new BigDecimal("100")));
Copy the code

So the next question, is it ok to use BigDecimal? Not really!

Let’s take a look at the source code for BigDecimal. There is one area that needs to be noted:

So if you look at these two properties, scale is the number of places to the right of the decimal point, and precision is the precision, which is what we call the effective length.

We already know that BigDecimal must pass in a string value, so what if we are now a Double value? Take a look at this with a simple test:

 private static void testScale(a) {
     BigDecimal bigDecimal1 = new BigDecimal(Double.toString(100));
     BigDecimal bigDecimal2 = new BigDecimal(String.valueOf(100d));
     BigDecimal bigDecimal3 = BigDecimal.valueOf(100d);
     BigDecimal bigDecimal4 = new BigDecimal("100");
     BigDecimal bigDecimal5 = new BigDecimal(String.valueOf(100));

     print(bigDecimal1);
     print(bigDecimal2);
     print(bigDecimal3);
     print(bigDecimal4);
     print(bigDecimal5);     
}

private static void print(BigDecimal bigDecimal) {
        System.out.println(String.format("scale %s precision %s result %s", bigDecimal.scale(), bigDecimal.precision(), bigDecimal.multiply(new BigDecimal("1.001"))));
}
Copy the code

The first three methods convert a double into a BigDecimal with a scale of 1 and a precision of 4, and the last two toString methods with a scale of 0 and a precision of 3. After multiplying with 1.001, we find that scale is the sum of the scales of two numbers.

When dealing with floating-point strings, we should explicitly use formatting expressions or formatting tools to specify decimals and rounding.

How to choose round and format floating point numbers?

Let’s first look at what happens when we use string. format to round. We know that there are two types of floating-point numbers: double and float.

double num1 = 3.35;
float num2 = 3.35 f;
System.out.println(String.format("%.1f", num1));
System.out.println(String.format("%.1f", num2));
Copy the code

The result seemed to have discrepancy with our expectations, but the problem is also very good explanation, the precision of the double and float is different, double the equivalent of 3.350000000000000088817841970012523233890533447265625 3.35, Float 3.35 is equivalent to 3.349999904632568359375. String.format is also used to round.

HALF_UP rounding is used by default in the Formatter class, and can be set manually if we need to use another rounding method for formatting.

At this point we know that string. format is a bit of a loophole, so we have to use BigDecimal for floating-point String formatting.

Let’s test the code to see if this is the case:

BigDecimal num1 = new BigDecimal("3.35"); // Round down BigDecimal num2 = num1.setScale(1, BigDecimal.ROUND_DOWN); System.out.println(num2); ROUND_HALF_UP BigDecimal num3 = num1.setScale(1, BigDecimal.ROUND_HALF_UP); System.out.println(num3); Input result: 3.3 3.4Copy the code

The results were in line with our expectations.

BigDecimal can’t compare using equals?

We all know that equals and not == are used for comparison of wrapped classes, so does this also apply in Bigdecimal? Data speak, a simple test to illustrate:

System.out.println(new BigDecimal("1").equals(new BigDecimal("1.0"))) result: falseCopy the code

We understand that 1 and 1.0 are equal, and should be, but Bigdecimal’s equals compares not only value, but also scale, which we said is the number of decimal places. Obviously, the two values have different number of decimal places, so the result is false.

In practical use, we often only want to compare the values of two BigDecimal values, note that the compareTo method is used:

System.out.println(new BigDecimal("1").compareto (new BigDecimal("1.0"))) result: trueCopy the code

The last

To sum up today’s post:

  • Avoid using doubles for operations
  • BigDecimal is initialized using either the String input parameter or bigdecimal.valueof ()
  • BigDecimal is recommended for formatting floating-point numbers
  • CompareTo is used to compare the values of two BigDecimal

More work articles can pay attention to the public number hometown learning Java access.