Project-driven learning, practice to test knowledge
preface
Many systems have the need to “process money”, such as e-commerce systems, financial systems, cash registers, and so on. As long as it is related to money, it has to be treated with a hundred and twenty thousand spirit, a little can not go wrong, otherwise it is a disaster for the system and users.
There are two main aspects to ensure the accuracy of the amount: overflow and accuracy. An overflow means that there is enough space for storing data, not too much. Precision means no deviation in the calculation of the amount, no more or less.
The overflow problem is solved by choosing a numeric type with a number of digits, i.e. not a float but a double. The precision problem, on the other hand, can’t be solved by double, because floating-point numbers lose precision.
Let’s visualize the loss of precision:
double money = 1.0 - 0.9;
Copy the code
Everyone knows it should be 0.1, but the actual result is 0.09999999999999998. This happens because the underlying computer is binary arithmetic, and binary arithmetic is not an accurate representation of decimal numbers. So in precise calculations such as commercial calculations, use other data types to ensure accuracy is not lost, and never use floating point numbers.
This article will go into more detail on how to do business calculations in real development, and put all the code and SQL statements on Github, which can be cloned and run.
The solution
There are two data types that can meet the needs of business computing, the first being, naturally, a Decimal type designed specifically for business computing, and the second being fixed-length integers.
Decimal
When choosing a data type, consider the database and the programming language. That is, what type is used to store data in the database and what type is used to process data in the code.
The database level is naturally a place to use the Decimal type because there is no loss of precision and it is perfectly suitable for commercial calculations.
The syntax for defining a field as decimal is decimal(M,N), where M represents how many bits to store and N represents how many bits to store as a decimal. Assuming decimal(20,2), this means that a total of 20 digits are stored, of which two are decimals.
Create a new user table with two simple fields, primary key and balance:
Here, the decimal position of 2 points is reserved, indicating that the amount is only stored in minutes. In the actual project, the unit to be stored can be determined according to the business needs, which is ok.
The Java equivalent for database Decimal is the java.math.BigDecimal type, which also guarantees complete accuracy.
There are three main ways to create BigDecimal:
BigDecimal d1 = new BigDecimal(0.1); // BigDecimal(double val)
BigDecimal d2 = new BigDecimal("0.1"); // BigDecimal(String val)
BigDecimal d3 = BigDecimal.valueOf(0.1); // static BigDecimal valueOf(double val)
Copy the code
The first two are constructors and the last one is static. All three methods are very convenient, but the first method is forbidden! Take a look at the print results of each of the three objects to see why:
D1, d2:0.1000000000000000055511151231257827021181583404541015625 d3 0.1:0.1Copy the code
The first method passes a parameter of type double through a constructor that does not get an exact value. To create A BigDecimal correctly, either convert the double to a string and call the constructor, or call the static method directly. In fact, inside the static method is the constructor that converts a double to a string and then calls it:
We don’t have to worry about the fact that if small values are queried from the database or passed in from the front end, the data will be mapped exactly to BigDecimal objects.
With creation behind us, we move on to the most important numeric operations. Arithmetic is nothing more than addition, subtraction, multiplication, and division, and these BigDecimal provide corresponding methods:
BigDecimal add(BigDecimal); / / add
BigDecimal subtract(BigDecimal); / /
BigDecimal multiply(BigDecimal); / / by
BigDecimal divide(BigDecimal); / / in addition to
Copy the code
BigDecimal is an immutable object, meaning that none of these operations change the value of the original object, and only a new object is returned after the method is executed. To update the original value after the operation, you can only reassign:
d1 = d1.subtract(d2);
Copy the code
Let’s verify whether the accuracy will be lost:
BigDecimal d1 = new BigDecimal("1.0");
BigDecimal d2 = new BigDecimal("0.9");
System.out.println(d1.subtract(d2));
Copy the code
The output is, of course, 0.1.
On the code side, accuracy is guaranteed, but on the mathematical side, division can be overdone. For example, dividing 10 by 3 raises the following exception:
In order to solve the problem of infinite decimals caused by endless division, we need to control the precision of decimals manually. Another method of division is to control precision:
BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
Copy the code
The scale parameter indicates how many decimals are reserved after the operation, and the roundingMode parameter indicates how to calculate the decimals.
BigDecimal d1 = new BigDecimal("1.0");
BigDecimal d2 = new BigDecimal("3");
System.out.println(d1.divide(d2, 2, RoundingMode.DOWN)); // The decimal precision is 2. The output is 0.33
Copy the code
RoundingMode enumeration can easily specify the decimal operation mode, in addition to directly off, and round, round up and other ways, according to the specific business needs to specify.
Note that decimal precision should be controlled as much as possible in the code, not in the database. By default, the database uses rounding to retain decimal accuracy.
For example, if THE decimal precision is set to 2 in the database and I store 0.335, the final value will be 0.34.
Now that we know how to create and evaluate BigDecimal objects, we are left with one last operation: compare. Because it is not a basic data types, with double = = sign can’t be sure, that we try to use the equals comparison:
BigDecimal d1 = new BigDecimal("0.33");
BigDecimal d2 = new BigDecimal("0.3300");
System.out.println(d1.equals(d2)); // false
Copy the code
The output is false because BigDecimal’s Equals method compares not only values, but also precision, even if the values are the same but the precision is different. To determine whether the values are the same, use the int compareTo(BigDecimal Val) method:
BigDecimal d1 = new BigDecimal("0.33");
BigDecimal d2 = new BigDecimal("0.3300");
System.out.println(d1.compareTo(d2) == 0); // true
Copy the code
If d1 is greater than d2, return 1;
D1 is less than d2, return -1;
If the two values are equal, return 0.
So much for the use of BigDecimal, let’s move on to the second solution.
On long integer
A fixed-length integer, as the name implies, is an integer of a fixed (decimal) length. It’s just a concept, it’s not a new data type, we’re still using regular integers.
The amount seems to have to be a small number, but a little reflection will show that the decimal is not necessary. The amount we demonstrated earlier is in yuan, 1.55 is $1.55. So if we were in corners, the value of $1.55 would be 15.5. If you scale it down to minutes, you get 155. Yes, as long as the smallest unit, the decimal can be omitted! This minimum unit depends on the business requirements, such as the system requirements to be accurate to the centimeter, then the value is 1550. Of course, it’s usually accurate to minutes, and we’re going to do it in minutes.
Create a new field with type bigint and unit of minutes:
The corresponding data type in the code is naturally Long. The basic types of numeric operations are familiar enough to use the operator:
long d1 = 10000L; / / 100 yuan
d1 += 500L; / / and five yuan
d1 -= 500L; / / minus five yuan
Copy the code
There is no need to add or subtract. Multiplication and division can be decimal cases, such as 20% off an item, and the operation is multiplied by 0.8:
long d1 = 2366L; / / 23.66 yuan
double result = d1 * 0.8; // 20% off, the result is 1892.8
d1 = (long)result; // Convert to an integer, excluding all decimals, with a value of 1892. Is 18.92 yuan
Copy the code
When we do decimals, the type naturally changes to floating point, so we also need to convert floating point numbers to integers.
Strong transfers eliminate all decimals, which does not represent a loss of precision. Whatever the minimum unit is required by the business, we only keep it. We don’t need to keep any unit below the point. This is consistent with BigDecimal; if only the decimal is needed in the system, the decimal precision is 2, and the rest of the decimal is discarded.
However, some business calculations may require other operations such as rounding, which we can do with the Math class:
long d1 = 2366L; / / 23.66 yuan
double result = d1 * 0.8; // The result is 1892.8
d1 = (long)result; // Skip all decimals, 1892
d1 = (long)Math.ceil(result); // Round up to 1893
d1 = (long)Math.round(result); // The value is rounded to 1893.Copy the code
Now let’s do division. When an integer is divided by an integer, all decimals are automatically dropped:
long d1 = 2366L;
long result = d1 / 3; // The correct value should have been 788.66666666666, leaving out all the decimals and ending up at 788
Copy the code
If some other decimal operation, such as rounding, is performed, the operation is performed on a floating point number and then converted to an integer:
long d1 = 2366L;
double result = d1 / 3.0; // Note that this is not divided by 3, but by 3.0 floating-point numbers
d1 = (long)Math.round(result); // The final value is 789, that is 7.89 yuan
Copy the code
Although database storage and code operations are integers, it is not user-friendly to display them in fractions. Therefore, after the back-end passes the value to the front-end, the front-end needs to divide the value by 100 and display it to the user in units of yuan. And then when the front end passes the value to the back end, it passes the value as an agreed integer.
finishing
That’s all about the amount processing. We learned two business computing scenarios:
- A Decimal type
- On long integer
Business calculations are not technically difficult, but if not done correctly, they can cause untold damage. After all, money matters are not trivial.
In order to facilitate everyone’s understanding, this paper omitted the content of front-end and back-end joint tuning and database operation. However, since it is a project practice, there must be a complete project, so this crab based on Spring Boot to build a complete Web project, database operations and interfaces have been written, SQL statements also have, Github warehouse clone can feel how to use in the real project knowledge of this article. There are many other project practices in the warehouse, covering all aspects of the business, and some of the modules are of sufficient quality to run a single warehouse, eliminating the need to find framework demos and scaffolding. Welcome star, crab will update more project practices!
I am RudeCrab, a RudeCrab who pursues simple and crude explanations of technique.
Follow the “RudeCrab” wechat official account to bully with crabs.