当前位置:网站首页>BigDecimal pit summary & Best Practices
BigDecimal pit summary & Best Practices
2022-07-27 04:20:00 【Jiang Hao】
BigDecimal Pit summary & Best practices
One 、 summary
Java stay java.math Provided in the package API class BigDecimal, Used for more than 16 The number of significant bits of a bit is calculated precisely . Double precision floating point variables double Can handle 16 Bit significant number , But in practice , It may be necessary to calculate and process larger or smaller numbers .
In general , For those numbers that do not need accurate calculation accuracy , We can use it directly Float and Double Handle , however Double.valueOf(String) and Float.valueOf(String) Will lose precision . So in development , If we need accurate results , Must be used BigDecimal Class .
BigDecimal What is created is an object , So we can't use the traditional +、-、*、/ And so on arithmetic operator directly carries on the mathematics operation to its object , You have to call its corresponding method . The parameter in the method must also be BigDecimal The object of . A constructor is a special method of a class , Specifically for creating objects , Especially objects with parameters .
Two 、 Use BigDecimal Ten common pits
2.1) establish BigDecimal object
BigDecimal Common constructors are as follows :
// Create an object with the integer value specified by the parameter
BigDecimal(int)
// Create an object with the double value specified by the parameter
BigDecimal(double)
// Create an object with the long integer value specified by the parameter
BigDecimal(long)
// Create an object with a string value specified by the parameter
BigDecimal(String)
Examples of use :
BigDecimal bd1 = new BigDecimal(0.02);
BigDecimal bd2 = BigDecimal.valueOf(0.02);
BigDecimal bd3 = new BigDecimal("0.02");
BigDecimal bd4 = new BigDecimal(Double.toString(0.02));
System.out.println("bd1 = " + bd1);
System.out.println("bd2 = " + bd2);
System.out.println("bd3 = " + bd3);
System.out.println("bd4 = " + bd4);
Output is as follows :
bd1 = 0.0200000000000000004163336342344337026588618755340576171875
bd2 = 0.02
bd3 = 0.02
bd4 = 0.02
The analysis reason :
Parameter type is double The result of the construction method of is unpredictable . Some people may think that in Java writes newBigDecimal(0.2) Created BigDecimal It's exactly the same as 0.2( Non scale value 1, Its scale is 1), But it's actually equal to 0.0200000000000000004163336342344337026588618755340576171875. This is because 0.2 It can't be expressed exactly as double( Or in this case , Can't be expressed as any finite length binary decimal ). such , The value passed into the constructor will not exactly equal 0.2( Although on the surface it's equal to the value ).
String The construction method is completely predictable : write in newBigDecimal(“0.2”) Will create a BigDecimal, It's exactly what's expected 0.2. therefore , comparison , It is usually recommended to give priority to String Construction method , If the parameter is a string, there will be no precision problem .
When double Must be used as BigDecimal Source time of , Please note that , This construction method provides an accurate transformation ; It does not provide the same results as the following operations : First use Double.toString(double) Method , And then use BigDecimal(String) Construction method , take double Convert to String. To get the result , Please use static valueOf(double) Method , It can also be done through BigDecimal.valueOf(double value) Static methods to create objects .
2.2)BigDecimal immutable
BigDecimal and String It also has object immutable rows , Once assigned, it will not change , Even if you do addition, subtraction, multiplication and division ; Examples of use :
BigDecimal count = new BigDecimal("3.1415");
count.add(new BigDecimal("0.1645"));
System.out.println("count:" + count); // count:3.1415
// Not in count Do calculations based on
System.out.println("result:" + count.add(new BigDecimal("0.1645"))); // result:3.3060
Output is as follows :
count:3.1415
result:3.3060
2.3) Keep the decimal places
BigDecimal Keep the decimal places , It mainly uses setScale Method :
public BigDecimal setScale(int newScale)
public BigDecimal setScale(int newScale, int roundingMode)
public BigDecimal setScale(int newScale, RoundingMode roundingMode)
The first 2 A parameter description :
ROUND_CEILING // Round to positive infinity
ROUND_DOWN // Round to zero
ROUND_FLOOR // Round to negative infinity
ROUND_HALF_DOWN // towards ( distance ) The nearest side is rounded , Except on both sides ( Distance of ) It's equal , If so , Round down , for example 1.55 Leave one decimal place and the result is 1.5
ROUND_HALF_EVEN // towards ( distance ) The nearest side is rounded , Except on both sides ( Distance of ) It's equal , If so , If the number of reserved digits is odd , Use ROUND_HALF_UP, If it's even , Use ROUND_HALF_DOWN
ROUND_HALF_UP // towards ( distance ) The nearest side is rounded , Except on both sides ( Distance of ) It's equal , If so , Round up , 1.55 Leave one decimal place and the result is 1.6
ROUND_UNNECESSARY // The results are accurate , There's no rounding pattern
ROUND_UP // To stay away from 0 Rounding in the direction of
Examples of use :
BigDecimal b = new BigDecimal("1.6666");
System.out.println("result b:" + b.setScale(2, BigDecimal.ROUND_HALF_UP)); // 1.67
System.out.println("result b:" + b.setScale(2)); // Accuracy error
Output is as follows :
result b:1.67
Exception in thread "main" java.lang.ArithmeticException: Rounding necessary
The analysis reason :
setScale Method is used by default roundingMode yes ROUND_UNNECESSARY, There is no need to use rounding mode , Setting accuracy 2 position , After the decimal point 4 Bit will definitely throw exception .
2.4)BigDecimal Turn it back String
BigDecimal Turn it back String There are three ways :
// Use scientific counting when necessary
String toString();
// No scientific counting
String toPlainString();
// The method of recording numbers often used in engineering calculation , Similar to scientific counting , But ask for 10 The power of must be 3 Multiple
String toEngineeringString();
Examples of use :
BigDecimal d = BigDecimal.valueOf(12334535345456700.12345634534534578901);
String out = d.toString(); // Or perform any formatting that needs to be done
System.out.println(out);
Output is as follows :
1.23345353454567E+16
The analysis reason :
default toString The result of the method is converted into scientific counting , Recommended toPlainString
2.5)BigDecimal Equivalence comparison
BigDecimal Provides equals、compareTo The two methods can be compared ; Examples of use :
BigDecimal bd5 = new BigDecimal("2.0");
BigDecimal bd6 = new BigDecimal("2.00");
System.out.println(bd5.equals(bd6));
System.out.println(bd5.compareTo(bd6));
Output results :
false
true
The analysis reason :
BigDecimal in equals The implementation of the method will compare the accuracy of two numbers , and compareTo Method will only compare the size of the value .
2.6) Use BigDecimal The parameter cannot be NULL
In the use of BigDecimal When calculating by type , add 、 reduce 、 ride 、 except 、 When it's bigger , Make sure that the two values involved in the calculation cannot be empty , Otherwise it will throw java.lang.NullPointerException abnormal . Examples of use :
BigDecimal number1 = new BigDecimal("88.88");
BigDecimal number2 = null;
BigDecimal number3 = number1.add(number2);
System.out.println("number1 add number2 = " + number3);
Output :
Exception in thread "main" java.lang.NullPointerException
2.7) Use BigDecimal The dividend cannot be 0
Examples of use :
BigDecimal number4 = new BigDecimal("88.88");
BigDecimal number5 = BigDecimal.ZERO;
BigDecimal number6 = number4.divide(number5);
System.out.println("number5 divide number6 = " + number6);
Output :
Exception in thread "main" java.lang.ArithmeticException: Division by zero
2.8) Use divide Method the result of the method is infinite circular decimal
Use divide Methods pay special attention to the setting accuracy ; Examples of use :
// Tax included amount
BigDecimal inclusiveTaxAmount = new BigDecimal("1000");
// tax rate
BigDecimal taxRate = new BigDecimal("0.13");
// Amount excluding tax = Tax included amount / (1+ tax rate )
BigDecimal exclusiveTaxAmount = inclusiveTaxAmount.divide(BigDecimal.ONE.add(taxRate));
System.out.println(exclusiveTaxAmount);
Output results :
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
The analysis reason :
The reason for the error is that it cannot be divided completely , The result is an infinite circular decimal : About this exception ,Oracle The official documents of have specific instructions
If the quotient has a nonterminating decimal expansion and the operation is specified to return an exact result, an ArithmeticException is thrown. Otherwise, the exact result of the division is returned, as done for other operations.
The main idea is , If the result of the quotient of division is an infinite decimal, but we expect to return the exact result , Then the program will throw an exception . Back to our example , We need to tell JVM We don't need to return exact results
Solution :
The solution is to specify the lower rounding mode , Requirements are reserved 2 Decimal place , rounding :
// Amount excluding tax = Tax included amount / (1+ tax rate )
BigDecimal exclusiveTaxAmount = inclusiveTaxAmount.divide(BigDecimal.ONE.add(taxRate), 2, RoundingMode.HALF_UP);
The output is :884.96
2.9) The execution sequence cannot be changed ( The commutative law of multiplication fails )
It is common sense that multiplication satisfies the commutative law , But in the world of computers , There will be situations that do not satisfy the commutative law of multiplication ; Examples of use :
BigDecimal h = BigDecimal.valueOf(1.0);
BigDecimal i = BigDecimal.valueOf(3.0);
BigDecimal j = BigDecimal.valueOf(3.0);
System.out.println(h.divide(i, 2, RoundingMode.HALF_UP).multiply(j)); // 0.990
System.out.println(h.multiply(j).divide(i, 2, RoundingMode.HALF_UP)); // 1.00
Output results :
0.990
1.00
Don't underestimate this 0.01 The difference between , In Huijin , There will be a very large amount difference , It is very easy to cause asset losses , The order of use is suggested to multiply first and then divide .
2.10)BigDecimal format
because NumberFormat Class format() Methods can be used BigDecimal Object as its parameter , You can use BigDecimal To exceed 16 The currency value of a significant number , Percentage value , As well as general numerical value for formatting control .
To take advantage of BigDecimal Format currency and percentage as an example . First , establish BigDecimal object , Conduct BigDecimal After the arithmetic operation of , Create references to currency and percentage formatting, respectively , The use of BigDecimal Object as format() Method parameters , Output its formatted currency value and percentage .
Examples of use :
// Create currency formatting references
NumberFormat currency = NumberFormat.getCurrencyInstance();
// Create percentage formatting references
NumberFormat percent = NumberFormat.getPercentInstance();
// Percentages have the most decimal points 3 position
percent.setMaximumFractionDigits(3);
// Loan amount
BigDecimal loanAmount = new BigDecimal("15000.48");
// The interest rate
BigDecimal interestRate = new BigDecimal("0.008");
// Multiply
BigDecimal interest = loanAmount.multiply(interestRate);
System.out.println(" Loan amount :\t" + currency.format(loanAmount));
System.out.println(" The interest rate :\t" + percent.format(interestRate));
System.out.println(" interest :\t" + currency.format(interest));
Output results :
Loan amount : ¥15,000.48
The interest rate : 0.8%
interest : ¥120.00
3、 ... and 、 Best practices
About the amount calculation , Many business teams will be based on BigDecimal One more Money class , In fact, we can directly use a semi official Money class :JSR 354 , Although not in Java 9 To become Java standard , It is likely to be integrated into subsequent Java Become the official library in version . Of course, there are several based on the amount Money Third party library components use , such as joda-money,hutool Also provided Money, The three are encapsulated Money The functions are not much different ,hutool It is more suitable for Chinese .
Specific use , We need to introduce the following maven rely on :
<dependency>
<artifactId>hutool-all</artifactId>
<groupId>cn.hutool</groupId>
<version>5.7.7</version>
</dependency>
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
3.1) Why is it recommended to use Money
Currency category encapsulates currency amount and currency . The current amount is long Type said , The unit is the smallest monetary unit of the currency ( RMB is cents ). at present ,Money Currency realizes the following main functions :
Support currency objects and double(float)/long(int)/String/BigDecimal Switch between ;
The currency class provides and JDK Medium BigDecimal Similar operation interface ,BigDecimal The operation interface of supports the operation function of arbitrary specified precision , Able to support all possible financial rules ;
Currency class also provides a set of simple operation interfaces , Use this set of arithmetic interfaces , The default processing rules are used in precision processing ;
Provide basic formatting functions ;
Money Class does not contain business-related statistical functions and formatting functions . Business related functions are recommended utility Class to achieve ;
Money Class implements the Serializable Interface , Support as parameters and return values of remote calls ;
Money Class implements the equals and hashCode Method .
3.2) Why not recommend BigDecimal
It is not recommended to use BigDecimal The reason is :
- Use BigDecimal, Currency of the same amount and currency BigDecimal There are many possible representations , for example :new BigDecimal(“10.5”) And new BigDecimal(“10.50”) It's not equal , because scale Unequal . bring Money class , There is only one way to express a currency with the same amount and currency ,new Money(“10.5”) and new Money(“10.50”) It should be equal ;
- BigDecimal yes Immutable, Once created, it cannot be changed , Yes BigDecimal Any operation will generate a new BigDecimal object , Therefore, the performance of mass statistics is not satisfactory .Money Class is mutable Of , Provide better support for mass statistics .
3.3) Examples of use
With javamoney As an example, let's introduce how to use : newly build Money class
CurrencyUnit cny = Monetary.getCurrency("CNY");
Money money = Money.of(1.0, cny);
// perhaps Money money = Money.of(1.0, "CNY");
//System.out.println(money);
Amount calculation
CurrencyUnit cny = Monetary.getCurrency("CNY");
Money oneYuan = Money.of(1.0, cny);
Money threeYuan = oneYuan.add(Money.of(2.0, "CNY")); //CNY 3
Money tenYuan = oneYuan.multiply(10); // CNY 10
Money fiveFen = oneYuan.divide(2); //CNY 0.5
More equal
Money fiveFen = Money.of(0.5, "CNY"); //CNY 0.5
Money anotherFiveFen = Money.of(0.50, "CNY"); // CNY 0.50
System.out.println(fiveFen.equals(anotherFiveFen)); // true
You can see , This class abstracts the amount explicitly , Increase the unit of the amount , It also avoids direct use BigDecimal Some pits .
3.4) How to save the database Money
Generally, the amount used in the database table structure is defined as decimal type ,Java Object property is BigDecimal,Mybatis The transformation mapping will be carried out automatically , If used Money How do wrapper classes perform transformation mapping , You can implement a Mybatis Of TypeHandler. Specifically TypeHandler The definition is as follows :
public class MoneyTypeHandler extends BaseTypeHandler<Money> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Money parameter, JdbcType jdbcType) throws SQLException {
ps.setLong(i, parameter.getAmountMinorLong());
}
@Override
public Money getNullableResult(ResultSet rs, String columnName) throws SQLException {
return parseMoney(rs.getLong(columnName));
}
@Override
public Money getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return parseMoney(rs.getLong(columnIndex));
}
@Override
public Money getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return parseMoney(cs.getLong(columnIndex));
}
private Money parseMoney(long value) {
return Money.of(CurrencyUnit.of("CNY"), value / 100.00);
}
}
stay springboot Application in the application.properties Add the following configuration :
# Appoint handler Bag location
mybatis.type-handlers-package=com.zhichubao.demo.handler
# Enable the processing of underscores in entities and databases
mybatis.configuration.map-underscore-to-camel-case=true
above MoneyTypeHandler The role is overall , If the field to be converted is a specified field , stay mapper Fields to be converted in , Appoint typeHandler attribute . Specific examples are as follows :
@Insert("insert into goods (name, create_time, update_time, price, price1)"
+ "values (#{name}, now(), now(), #{price, typeHandler=com.example.demo.handler.MoneyTypeHandler}, " +
" #{price1, typeHandler=com.example.demo.handler.MoneyTypeHandler})")
@Options(useGeneratedKeys = true)
int save(Goods goods);
@Select("select * from goods where id = #{id}")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "price", property = "price", typeHandler = MoneyTypeHandler.class),
@Result(column = "price1", property = "price1", typeHandler = MoneyTypeHandler.class)
})
Goods findById(@Param("id") Long id);
Four 、 summary
This article introduces BigDecimal The pit of the scene in use , And based on these pits, we get “ Best practices ”. Although it is recommended to use in some scenarios BigDecimal, It can achieve better accuracy , But compared with double and float, There is still a certain loss , Especially in dealing with huge , It's especially obvious when it comes to complex operations . Therefore, it is unnecessary to use the general accuracy calculation BigDecimal. When it must be used , Be sure to avoid the above pit .
Finally, I strongly recommend Money,Money When in use, it can automatically avoid many of the above pits , To a certain extent, it can avoid anomalies and financial asset losses .
边栏推荐
- 新互联网时代已来 WEB 3.0 会给我们带来哪些新机遇
- Use tag tag in golang structure
- H. 265 web player easyplayer's method of opening video to the public
- Nacos启动与登录
- 「Gonna Be Alright 会好的」数藏现已开售!感受艺术家的心灵共鸣
- 整理字符串
- centos如何安装mysqldump
- Manually build ABP framework from 0 -abp official complete solution and manually build simplified solution practice
- 356页14万字高端商业办公综合楼弱电智能化系统2022版
- 面试题 02.05. 链表求和
猜你喜欢

11. Zuul routing gateway

First pass of routing strategy
![[small sample segmentation] msanet: multi similarity and attention guidance for boosting few shot segmentation](/img/b9/270e0f20586a953e83a18f7fac155f.png)
[small sample segmentation] msanet: multi similarity and attention guidance for boosting few shot segmentation

JVM原理简介

Subject 3: Jinan Zhangqiu line 6

2022 operation of simulated examination question bank and simulated examination platform for safety production management personnel of hazardous chemical production units

js三种遍历数组的方法:map、forEach、filter

Skywalking distributed system application performance monitoring tool - medium

What is animation effect? What is the transition effect?

PSINS工具箱中轨迹生成工具详细解析
随机推荐
【SemiDrive源码分析】【驱动BringUp】41 - LCM 驱动 backlight 背光控制原理分析
influxDB 基础了解
Big talk · book sharing | lean product development: principles, methods and Implementation
【机器学习网络】BP神经网络与深度学习-6 深度神经网络(deep neural Networks DNN)
Parallels Desktop启动虚拟机“操作失败”问题解决
JVM调优中的一些常见指令
CloudCompare&PCL 匹配点中值(或标准差)距离抑制
H. 265 web player easyplayer's method of opening video to the public
[MySQL series] MySQL index transactions
Learning route from junior programmer to architect + complete version of supporting learning resources
List Simulation Implementation
通信协议综述
js实现页面跳转与参数获取加载
【MySQL系列】MySQL索引事务
CloudCompare&PCL 匹配点距离抑制
Elastic开源社区:开发者招募
网工知识角|只需四个步骤,教会你使用SecureCRT连接到eNSP,常用工具操作指南必看
webpack打包vue项目添加混淆方式,解决缓存问题
Using LCD1602 to display ultrasonic ranging
Towhee weekly model