问题描述
在某个需求中,需要判断申请值是不是高于预估值,如果过大需要拦截,以下代码属于简化后的代码,
其中从外部获取了double类型的86280和String类型的71900两个参数。1
2
3
4
5
6
7
8
9
10public static void main(String[] args) {
	BigDecimal b1=new BigDecimal(86280);
	String price="71900";
	BigDecimal b2=new BigDecimal(price);
	b2=b2.multiply(new BigDecimal(1.2));
	if(b1.compareTo(b2)==1){
		System.out.println("参数不合法");
	}
	System.out.println(71900*1.2);
}
根据预估,如果两个值相等是需要放过的,但是测试环境却发生了拦截。
分析
经过debug发现是精度问题,b2=b2.multiply(new BigDecimal(1.2)); 运行的时候会产生精度丢失变成86279.999999999,
认为BigDecimal会出现精度丢失的问题,但经过查询和之前的理解,该类确实不会出现精度问题。
罪魁祸首
1  | new BigDecimal(1.2);  | 
在进行惩罚的时候因为不能传Double所以用构造参数构造了一个BigDecimal,但是new BigDecimal(Double double)这个构造器是不可靠的,
点开该方法的注释,也描述了这个问题(或者是这种设计)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47/**
 * Translates a {@code double} into a {@code BigDecimal} which
 * is the exact decimal representation of the {@code double}'s
 * binary floating-point value.  The scale of the returned
 * {@code BigDecimal} is the smallest value such that
 * <tt>(10<sup>scale</sup> × val)</tt> is an integer.
 * <p>
 * <b>Notes:</b>
 * <ol>
 * <li>
 * The results of this constructor can be somewhat unpredictable.
 * One might assume that writing {@code new BigDecimal(0.1)} in
 * Java creates a {@code BigDecimal} which is exactly equal to
 * 0.1 (an unscaled value of 1, with a scale of 1), but it is
 * actually equal to
 * 0.1000000000000000055511151231257827021181583404541015625.
 * This is because 0.1 cannot be represented exactly as a
 * {@code double} (or, for that matter, as a binary fraction of
 * any finite length).  Thus, the value that is being passed
 * <i>in</i> to the constructor is not exactly equal to 0.1,
 * appearances notwithstanding.
 *
 * <li>
 * The {@code String} constructor, on the other hand, is
 * perfectly predictable: writing {@code new BigDecimal("0.1")}
 * creates a {@code BigDecimal} which is <i>exactly</i> equal to
 * 0.1, as one would expect.  Therefore, it is generally
 * recommended that the {@linkplain #BigDecimal(String)
 * <tt>String</tt> constructor} be used in preference to this one.
 *
 * <li>
 * When a {@code double} must be used as a source for a
 * {@code BigDecimal}, note that this constructor provides an
 * exact conversion; it does not give the same result as
 * converting the {@code double} to a {@code String} using the
 * {@link Double#toString(double)} method and then using the
 * {@link #BigDecimal(String)} constructor.  To get that result,
 * use the {@code static} {@link #valueOf(double)} method.
 * </ol>
 *
 * @param val {@code double} value to be converted to
 *        {@code BigDecimal}.
 * @throws NumberFormatException if {@code val} is infinite or NaN.
 */
public BigDecimal(double val) {
    this(val,MathContext.UNLIMITED);
}
根据描述,如果需要精确运算,则需要使用Double.toString(Double double)作为参数,
而BigDecimal对float,double这种双精度类型,首推的构造器是BigDecimal.valueOf()
而真正丢失的原因是因为new BigDecimal(Double double)里的这个double已经发生了精度丢失,而BigDecimal只是原封不动的保留了双精度的值而已。